<!doctype html>
<html>
        <head>
                <meta charset="UTF-8">
                <title>Microdata tests</title>
                <script type="text/javascript" src="/resources/testharness.js"></script>
                <script type="text/javascript" src="/resources/testharnessreport.js"></script>
                <link rel="help" href="http://dev.w3.org/html5/md/#microdata-dom-api">
                <link rel="help" href="http://dev.w3.org/html5/md/#encoding-microdata">
        </head>
        <body>
                <noscript><p>Enable JavaScript and reload</p></noscript>
                <div id="log">Running test...</div>
                <div itemscope itemtype="http://example.com/bar data:text/plain, http://example.com/foo" id="one"></div>
                <div itemscope itemtype="http://example.com/bar" id="two"></div>
                <div itemscope itemtype="http://example.com/foo http://example.com/bar" id="three">
                        <div itemscope itemtype="http://example.com/bar data:text/plain," id="four"></div>
                </div>
                <div itemscope id="five"></div>
                <script type="text/javascript">
/* All tests are stand-alone.
To reduce this testsuite to show only a single desired test,
simply remove all test(...) blocks before and after it. */

var urls = {"base":"http://" + location.host + "/",
            "www":"http://www." + location.host + "/",
            "www1":"http://www1." + location.host + "/"}

function makeEl(eltype,props,contents) {
        var elem = document.createElement(eltype);
        for( var i in props ) {
                //just in case the framework extends object...
                if( props.hasOwnProperty(i) ) {
                        elem.setAttribute(i,props[i]);
                }
        }
        if( contents ) {
                elem.innerHTML = contents;
        }
        return elem;
}

/* getItem tests */
test(function () {
        assert_true( !!document.getItems );
}, 'document.getItems must exist');
test(function () {
        assert_true( document.getItems() instanceof NodeList, 'instanceof test' );
        NodeList.prototype.customProperty = true;
        assert_true( document.getItems().customProperty, 'inheritance test' );
}, 'document.getItems must return a NodeList');
test(function () {
        assert_equals( document.getItems().length, 5 );
}, 'document.getItems must locate the correct number of items');
test(function () {
        var nlist = document.getItems();
        var foo = makeEl('div',{itemscope:'itemscope'});
        document.body.appendChild(foo);
        var templength = nlist.length;
        document.body.removeChild(foo);
        assert_equals( templength, 6 );
        assert_equals( nlist.length, 5 );
}, 'document.getItems must return a live NodeList');
test(function () {
        var nlist = document.getItems();
        document.getElementById('one').removeAttribute('itemscope');
        var templength = nlist.length;
        document.getElementById('one').setAttribute('itemscope','itemscope');
        assert_equals( templength, 4 );
        assert_equals( nlist.length, 5 );
}, 'live NodeList must notice when itemscope changes');
test(function () {
        document.getElementById('one').removeAttribute('itemscope');
        var templength = document.getItems().length;
        document.getElementById('one').setAttribute('itemscope','itemscope');
        assert_equals( templength, 4 );
        assert_equals( document.getItems().length, 5 );
}, 'next request must notice when itemscope changes');
test(function () {
        assert_equals( document.getItems('http://example.com/').length, 0, 'http://example.com/' );
        assert_equals( document.getItems('example').length, 0, 'example' );
        assert_equals( document.getItems('http://example.com/foo').length, 2, 'http://example.com/foo' );
        assert_equals( document.getItems('http://example.com/bar').length, 4, 'http://example.com/bar' );
        assert_equals( document.getItems('data:text/plain,').length, 2, 'data:text/plain,' );
}, 'document.getItems must locate the right number of items for each itemtype');
test(function () {
        assert_equals( document.getItems('http://example.com/Foo').length, 0, 'http://example.com/Foo' );
        assert_equals( document.getItems('HTTP://example.com/foo').length, 0, 'HTTP://example.com/foo' );
}, 'document.getItems must be case sensitive');
test(function () {
        var nlist = document.getItems('http://example.com/foo');
        var foo = makeEl('div',{itemscope:'itemscope',itemtype:'http://example.com/foo'});
        document.body.appendChild(foo);
        var templength = nlist.length;
        document.body.removeChild(foo);
        assert_equals( templength, 3 );
        assert_equals( nlist.length, 2 );
}, 'document.getItems must return a live NodeList when using URLs');
test(function () {
        var nlist = document.getItems('http://example.com/foo');
        document.getElementById('one').removeAttribute('itemtype');
        var templength = nlist.length;
        document.getElementById('one').setAttribute('itemtype','http://example.com/bar data:text/plain, http://example.com/foo');
        assert_equals( templength, 1 );
        assert_equals( nlist.length, 2 );
}, 'live NodeList must notice when itemtype changes');
test(function () {
        document.getElementById('one').removeAttribute('itemtype');
        var templength = document.getItems('http://example.com/foo').length;
        document.getElementById('one').setAttribute('itemtype','http://example.com/bar data:text/plain, http://example.com/foo');
        assert_equals( templength, 1 );
        assert_equals( document.getItems('http://example.com/foo').length, 2 );
}, 'next request must notice when itemtype changes');
test(function () {
        assert_equals( document.getItems('http://example.com/foo data:text/plain,').length, 1, 'basic spaces' );
        assert_equals( document.getItems('  http://example.com/foo  data:text/plain,  ').length, 1, 'extraneous spaces' );
}, 'document.getItems must locate items when parameters are separated by spaces');
test(function () {
        assert_equals( document.getItems('http://example.com/foo data:text/plain, http://example.com/foo').length, 1 );
}, 'document.getItems must ignore duplicated tokens');
test(function () {
        var testitems = document.getItems('http://example.com/bar');
        assert_equals( testitems[0].id, 'one' );
        assert_equals( testitems[1].id, 'two' );
        assert_equals( testitems[2].id, 'three' );
        assert_equals( testitems[3].id, 'four' );
}, 'document.getItems NodeList must be in source tree order');
test(function () {
        assert_true( document.getItems('http://example.com/abc') != document.getItems('http://example.com/def'), 'different tokens' );
        assert_true( document.getItems() != document.getItems(' '), 'no tokens' );
}, 'document.getItems must not return the same NodeList for different parameters');
test(function () {
        assert_equals( document.getItems('').length, 5, 'empty string' );
        assert_equals( document.getItems('  ').length, 5, 'string with spaces' );
}, 'document.getItems must treat no tokens as no parameter');
//removed due to disputed Web compatibility of casting null with DOM methods
/*
test(function () {
        assert_equals( document.getItems(null).length, 0, 'null' );
        assert_equals( document.getItems(window.undefined).length, 0, 'undefined' );
}, 'document.getItems must cast null and undefined to strings');
*/
test(function () {
        var foo = makeEl('div',{itemtype:'http://example.com/foo'});
        document.body.appendChild(foo);
        var templength = document.getItems('http://example.com/foo').length;
        document.body.removeChild(foo);
        assert_equals( templength, 2 );
}, 'document.getItems must not find items with itemtype but not itemscope');
test(function () {
        var foo = makeEl('div',{itemscope:'itemscope',itemtype:'baz'}),
            bar = makeEl('div',{itemscope:'itemscope',itemtype:location.href.replace(/\/[^\/]*$/,'/baz')});
        document.body.appendChild(foo);
        document.body.appendChild(bar);
        var unrezlength = document.getItems('baz').length;
        var rezlength = document.getItems(location.href.replace(/\/[^\/]*$/,'/baz')).length;
        document.body.removeChild(foo);
        document.body.removeChild(bar);
        assert_equals( unrezlength, 1, 'unresolved URL' );
        assert_equals( rezlength, 1, 'resolved URL' );
}, 'document.getItems and itemtype must not resolve URLs');
test(function () {
        document.getElementById('one').setAttribute('itemprop','test');
        document.getElementById('four').setAttribute('itemprop','test');
        var templength = document.getItems().length;
        document.getElementById('one').removeAttribute('itemprop');
        document.getElementById('four').removeAttribute('itemprop');
        assert_equals( templength, 3 );
}, 'document.getItems must not see items that have the itemprop attribute set');

/* itemScope property tests */
test(function () {
        assert_true(makeEl('div',{itemscope:'itemscope'}).itemScope);
        assert_false(makeEl('div',{}).itemScope);
}, 'the itemscope attribute must be reflected by the .itemScope property');
test(function () {
        assert_equals( typeof makeEl('div',{itemscope:'itemscope'}).itemScope, 'boolean', 'attribute exists' );
        assert_equals( typeof makeEl('div',{}).itemScope, 'boolean', 'attribute does not exist' );
}, 'the itemScope property must be boolean');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemScope = true;
        assert_true(testEl.itemScope,'writing true');
        testEl.itemScope = false;
        assert_false(testEl.itemScope,'writing false');
}, 'the itemScope property must be read/write');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemScope = true;
        assert_true(testEl.hasAttribute('itemscope'),'writing true');
        testEl.itemScope = false;
        assert_false(testEl.hasAttribute('itemscope'),'writing false');
}, 'writing to the itemScope property must toggle existence of the itemscope content attribute');
test(function () {
        var testEl = makeEl('div',{});
        document.body.appendChild(testEl);
        var numAppend = document.getItems().length;
        testEl.itemScope = true;
        var numTrue = document.getItems().length;
        testEl.itemScope = false;
        var numFalse = document.getItems().length;
        document.body.removeChild(testEl);
        assert_equals(numAppend,5,'after appending the new item');
        assert_equals(numTrue,6,'after setting the property to true');
        assert_equals(numFalse,5,'after setting the property to false');
}, 'writing to the itemScope property must affect whether the element is returned by getItems');
test(function () {
        var testEl = makeEl('div',{}), nlist = document.getItems();
        document.body.appendChild(testEl);
        var numAppend = nlist.length;
        testEl.itemScope = true;
        var numTrue = nlist.length;
        testEl.itemScope = false;
        var numFalse = nlist.length;
        document.body.removeChild(testEl);
        assert_equals(numAppend,5,'after appending the new item');
        assert_equals(numTrue,6,'after setting the property to true');
        assert_equals(numFalse,5,'after setting the property to false');
}, 'writing to the itemScope property must affect membership of live NodeLists');

/* itemType property tests (properties collection tests are done later) */
test(function () {
        assert_equals(makeEl('div',{}).itemType.toString(),'','no attribute');
        assert_equals(makeEl('div',{itemtype:' foo  bar '}).itemType.toString(),' foo  bar ','with simple tokens');
        var testEl = makeEl('div',{itemtype:'foo'});
        testEl.removeAttribute('itemtype');
        assert_equals(testEl.itemType.toString(),'','removed attribute');
}, 'the itemType attribute must be reflected by the .itemRef property');
test(function () {
        assert_equals( typeof makeEl('div',{}).itemType, 'object' );
}, 'the itemType property must be an object');
test(function () {
        assert_true( makeEl('div',{}).itemType instanceof DOMTokenList, 'instanceof test' );
        DOMTokenList.prototype.customProperty = true;
        assert_true( makeEl('div',{}).itemType.customProperty, 'inheritance test' );
}, 'the itemType property must implement DOMTokenList');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.itemType, testEl.itemType );
}, 'the itemType property must always reference the same object');
test(function () {
        assert_equals( makeEl('div',{itemtype:'test test'}).itemType.length, 2, 'duplicates in initial string should be preserved' );
        assert_equals( makeEl('div',{itemtype:'test test'}).itemType.item(0), 'test' );
        assert_true( makeEl('div',{itemtype:'test test'}).itemType.contains('test') );
}, 'itemType must be correct for an element that has itemtype tokens');
test(function () {
        assert_equals( makeEl('div',{itemtype:' '}).itemType.length, 0 );
}, 'itemType.length must be 0 for an element that has no tokens');
test(function () {
        assert_false( makeEl('div',{itemtype:' '}).itemType.contains('foo') );
}, 'itemType must not contain an undefined class');
test(function () {
        assert_equals( makeEl('div',{itemtype:' '}).itemType.item(0), null );
}, 'itemType.item() must return null for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemtype:' '}).itemType.item(-1), null );
}, 'itemType.item() must return null for negative index');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemtype:' '}).itemType[0], window.undefined );
}, 'itemType[index] must be undefined for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemtype:' '}).itemType[-1], window.undefined );
}, 'itemType[index] must be undefined for negative index');
test(function () {
        assert_equals( makeEl('div',{itemType:' '}).itemType.toString(), ' ' );
}, 'empty itemType should stringify to contain the attribute\'s whitespace');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemtype:' '}).itemType.contains(''); } );
}, 'itemType.contains(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemtype:' '}).itemType.add(''); } );
}, 'itemType.add(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemtype:' '}).itemType.remove(''); } );
}, 'itemType.remove(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemtype:' '}).itemType.toggle(''); } );
}, 'itemType.toggle(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemtype:' '}).itemType.contains('a b'); } );
}, 'itemType.contains(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemtype:' '}).itemType.add('a b'); } );
}, 'itemType.add(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemtype:' '}).itemType.remove('a b'); } );
}, 'itemType.remove(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemtype:' '}).itemType.toggle('a b'); } );
}, 'itemType.toggle(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        var testEl = makeEl('div',{itemtype:'foo'});
        assert_true( testEl.itemType.contains('foo'), 'before change' );
        testEl.setAttribute('itemtype','bar');
        assert_true( testEl.itemType.contains('bar'), 'after change' );
        assert_false( testEl.itemType.contains('foo'), 'after change' );
}, 'itemType.contains must update when the underlying attribute is changed');
test(function () {
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('FOO') );
}, 'itemType.contains must be case sensitive');
test(function () {
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo.') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo)') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo\'') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo$') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo~') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo?') );
        assert_false( makeEl('div',{itemtype:'foo'}).itemType.contains('foo\\') );
}, 'itemType.contains must not match when punctuation characters are added');
test(function () {
        var elem = makeEl('div',{itemtype:'foo'});
        elem.itemType.add('FOO');
        assert_true( elem.itemType.contains('foo') );
}, 'itemType.add must not remove existing tokens');
test(function () {
        assert_true( makeEl('div',{itemtype:'foo FOO'}).itemType.contains('FOO') );
}, 'itemType.contains case sensitivity must match a case-specific string');
test(function () {
        assert_equals( makeEl('div',{itemtype:'foo FOO'}).itemType.length, 2 );
}, 'itemType.length must correctly reflect the number of tokens');
test(function () {
        assert_equals( makeEl('div',{itemtype:'foo FOO'}).itemType.item(0), 'foo' );
}, 'itemType.item(0) must return the first token');
test(function () {
        var elem = makeEl('div',{itemtype:'foo'});
        elem.itemType.add('FOO');
        assert_equals( elem.itemType.item(1), 'FOO' );
}, 'itemType.item must return case-sensitive strings and preserve token order');
test(function () {
        assert_equals( makeEl('div',{itemtype:'foo FOO'}).itemType[0], 'foo' );
}, 'itemType[0] must return the first token');
test(function () {
        assert_equals( makeEl('div',{itemtype:'foo FOO'}).itemType[1], 'FOO' );
}, 'itemType[index] must return case-sensitive strings and preserve token order');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemtype:'foo FOO'}).itemType[2], window.undefined );
}, 'itemType[index] must still be undefined for out-of-range index when earlier indexes exist');
test(function () {
        var elem = makeEl('div',{});
        elem.itemType.add('foo');
        elem.itemType.add('FOO');
        assert_equals( elem.getAttribute('itemtype'), 'foo FOO' );
}, 'itemtype attribute must update correctly when items have been added through itemType');
test(function () {
        var elem = makeEl('div',{});
        elem.itemType.add('foo');
        elem.itemType.add('FOO');
        assert_equals( elem.itemType + '', 'foo FOO' );
}, 'itemType must stringify correctly when items have been added');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.add('foo');
        assert_equals( elem.itemType.length, 2 );
        assert_equals( elem.itemType + '', 'foo FOO' );
}, 'itemType.add must not make any changes if an existing token is added');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.remove('bar');
        assert_equals( elem.itemType.length, 2 );
        assert_equals( elem.itemType + '', 'foo FOO' );
}, 'itemType.remove must not make any changes if a non-existing token is removed');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.remove('foo');
        assert_equals( elem.itemType.length, 1 );
        assert_equals( elem.itemType.toString(), 'FOO' );
        assert_false( elem.itemType.contains('foo') );
        assert_true( elem.itemType.contains('FOO') );
}, 'itemType.remove must remove existing tokens');
test(function () {
        var elem = makeEl('div',{itemtype:'test test'});
        elem.itemType.remove('test');
        assert_equals( elem.itemType.length, 0 );
        assert_false( elem.itemType.contains('test') );
}, 'itemType.remove must remove duplicated tokens');
test(function () {
        var elem = makeEl('div',{itemtype:'token1 token2 token3'});
        elem.itemType.remove('token2');
        assert_equals( elem.itemType.toString(), 'token1 token3' );
}, 'itemType.remove must collapse whitespace around removed tokens');
test(function () {
        var elem = makeEl('div',{itemtype:' token1 token2  '});
        elem.itemType.remove('token2');
        assert_equals( elem.itemType.toString(), 'token1' );
}, 'itemType.remove must remove all useless whitespace');
test(function () {
        var elem = makeEl('div',{itemtype:'  token1  token2  token3  '});
        elem.itemType.remove('token2');
        assert_equals( elem.itemType.toString(), 'token1 token3' );
}, 'itemType.remove must collapse multiple whitespace around removed tokens');
test(function () {
        var elem = makeEl('div',{itemtype:'  token1  token2  token1  '});
        elem.itemType.remove('token2');
        assert_equals( elem.itemType.toString(), 'token1' );
}, 'itemType.remove must remove duplicates when removing tokens');
test(function () {
        var elem = makeEl('div',{itemtype:'  token1  token2  token3  '});
        elem.itemType.remove('token1', 'token3');
        assert_equals( elem.itemType.toString(), 'token2' );
}, 'itemType.remove must collapse whitespace when removing multiple tokens');
test(function () {
        var elem = makeEl('div',{itemtype:'  token1  token2  '});
        elem.itemType.add('token1');
        assert_equals( elem.itemType.toString(), 'token1 token2' );
}, 'itemType.add must remove unused whitespace when the token already exists');
test(function () {
        var elem = makeEl('div',{itemtype:'FOO'});
        assert_true(elem.itemType.toggle('foo'));
        assert_equals( elem.itemType.length, 2 );
        assert_true( elem.itemType.contains('foo') );
        assert_true( elem.itemType.contains('FOO') );
}, 'itemType.toggle must toggle tokens case-sensitively when adding');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        assert_false(elem.itemType.toggle('foo'));
        assert_false(elem.itemType.toggle('FOO'));
        assert_false( elem.itemType.contains('foo') );
        assert_false( elem.itemType.contains('FOO') );
}, 'itemType.toggle must be able to remove tokens case-sensitively');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.toggle('foo');
        elem.itemType.toggle('FOO');
        assert_equals( elem.getAttribute('itemtype'), '' );
}, 'itemtype attribute must be empty when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.toggle('foo');
        elem.itemType.toggle('FOO');
        assert_equals( elem.itemType.toString(), '' );
}, 'itemType must stringify to an empty string when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.toggle('foo');
        elem.itemType.toggle('FOO');
        assert_equals( elem.itemType.item(0), null );
}, 'itemType.item(0) must return null when all classes have been removed');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        var elem = makeEl('div',{itemtype:'foo FOO'});
        elem.itemType.toggle('foo');
        elem.itemType.toggle('FOO');
        assert_equals( elem.itemType[0], window.undefined );
}, 'itemType[0] must be undefined when all classes have been removed');
//if the last character of DOMTokenSting underlying character is not a space character, append U+0020", where "space character" is from " \t\r\n\f"
test(function () {
        var elem = makeEl('div',{itemtype:'a '});
        elem.itemType.add('b');
        assert_equals(elem.itemType.toString(),'a b');
}, 'itemType.add should treat " " as a space');
test(function () {
        var elem = makeEl('div',{itemtype:'a\t'});
        elem.itemType.add('b');
        assert_equals(elem.itemType.toString(),'a b');
}, 'itemType.add should normalize \\t as a space');
test(function () {
        var elem = makeEl('div',{itemtype:'a\r'});
        elem.itemType.add('b');
        assert_equals(elem.itemType.toString(),'a b');
}, 'itemType.add should normalize \\r as a space');
test(function () {
        var elem = makeEl('div',{itemtype:'a\n'});
        elem.itemType.add('b');
        assert_equals(elem.itemType.toString(),'a b');
}, 'itemType.add should normalize \\n as a space');
test(function () {
        var elem = makeEl('div',{itemtype:'a\f'});
        elem.itemType.add('b');
        assert_equals(elem.itemType.toString(),'a b');
}, 'itemType.add should normalize \\f as a space');
test(function () {
        var elem = makeEl('div',{itemtype:'foo'});
        elem.itemType.remove('foo');
        elem.removeAttribute('itemtype');
        assert_true( elem.itemType.toggle('foo') );
}, 'itemType.toggle must work after removing the itemtype attribute');
test(function () {
        //WebIDL and ECMAScript 5 - a readonly property has a getter but not a setter
        //ES5 makes [[Put]] fail but not throw
        var failed = false;
        var elem = makeEl('div',{itemtype:'token1'});
        try {
                elem.itemType.length = 0;
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemType.length,1);
        assert_false(failed,'an error was thrown');
}, 'itemType.length must be read-only');
test(function () {
        var failed = false, elem = makeEl('div',{itemtype:'test'}), realList = elem.itemType;
        try {
                elem.itemType = 'dummy';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemType,realList);
        assert_equals(elem.itemType.toString(),'dummy','attempting to write should modify the underlying string');
        assert_false(failed,'an error was thrown');
}, 'itemType must be read-only');

/* itemProp property tests (properties collection tests are done later) */
test(function () {
        assert_equals(makeEl('div',{}).itemProp.toString(),'','no attribute');
        assert_equals(makeEl('div',{itemprop:' ' + urls.www1 + 'foo#bar  test '}).itemProp.toString(),' ' + urls.www1 + 'foo#bar  test ','with URL and simple tokens');
        var testEl = makeEl('div',{itemprop:urls.www1 + 'foo#bar'});
        testEl.removeAttribute('itemprop');
        assert_equals(testEl.itemProp.toString(),'','removed attribute');
}, 'the itemprop attribute must be reflected by the .itemProp property');
test(function () {
        assert_equals( typeof makeEl('div',{}).itemProp, 'object' );
}, 'the itemProp property must be an object');
test(function () {
        assert_true( makeEl('div',{}).itemProp instanceof DOMTokenList, 'instanceof test' );
        DOMTokenList.prototype.customProperty = true;
        assert_true( makeEl('div',{}).itemProp.customProperty, 'inheritance test' );
}, 'the itemProp property must implement DOMTokenList');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.itemProp, testEl.itemProp );
}, 'the itemProp property must always reference the same object');
test(function () {
        assert_equals( makeEl('div',{itemprop:'test test'}).itemProp.length, 2, 'duplicates in initial string should be preserved' );
        assert_equals( makeEl('div',{itemprop:'test test'}).itemProp.item(0), 'test' );
        assert_true( makeEl('div',{itemprop:'test test'}).itemProp.contains('test') );
}, 'itemProp must be correct for an element that has itemprop tokens');
test(function () {
        assert_equals( makeEl('div',{itemprop:' '}).itemProp.length, 0 );
}, 'itemProp.length must be 0 for an element that has no tokens');
test(function () {
        assert_false( makeEl('div',{itemprop:' '}).itemProp.contains('foo') );
}, 'itemProp must not contain an undefined class');
test(function () {
        assert_equals( makeEl('div',{itemprop:' '}).itemProp.item(0), null );
}, 'itemProp.item() must return null for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemprop:' '}).itemProp.item(-1), null );
}, 'itemProp.item() must return null for negative index');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemprop:' '}).itemProp[0], window.undefined );
}, 'itemProp[index] must be undefined for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemprop:' '}).itemProp[-1], window.undefined );
}, 'itemProp[index] must be undefined for negative index');
test(function () {
        assert_equals( makeEl('div',{itemprop:' '}).itemProp.toString(), ' ' );
}, 'empty itemProp should stringify to contain the attribute\'s whitespace');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.contains(''); } );
}, 'itemProp.contains(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.add(''); } );
}, 'itemProp.add(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.remove(''); } );
}, 'itemProp.remove(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.toggle(''); } );
}, 'itemProp.toggle(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.contains('a b'); } );
}, '.contains(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.add('a b'); } );
}, 'itemProp.add(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.remove('a b'); } );
}, 'itemProp.remove(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemprop:' '}).itemProp.toggle('a b'); } );
}, 'itemProp.toggle(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        var testEl = makeEl('div',{itemprop:'foo'});
        assert_true( testEl.itemProp.contains('foo'), 'before change' );
        testEl.setAttribute('itemprop','bar');
        assert_true( testEl.itemProp.contains('bar'), 'after change' );
        assert_false( testEl.itemProp.contains('foo'), 'after change' );
}, 'itemProp.contains must update when the underlying attribute is changed');
test(function () {
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('FOO') );
}, 'itemProp.contains must be case sensitive');
test(function () {
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo.') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo)') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo\'') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo$') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo~') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo?') );
        assert_false( makeEl('div',{itemprop:'foo'}).itemProp.contains('foo\\') );
}, 'itemProp.contains must not match when punctuation characters are added');
test(function () {
        var elem = makeEl('div',{itemprop:'foo'});
        elem.itemProp.add('FOO');
        assert_true( elem.itemProp.contains('foo') );
}, 'itemProp.add must not remove existing tokens');
test(function () {
        assert_true( makeEl('div',{itemprop:'foo FOO'}).itemProp.contains('FOO') );
}, 'itemProp.contains case sensitivity must match a case-specific string');
test(function () {
        assert_equals( makeEl('div',{itemprop:'foo FOO'}).itemProp.length, 2 );
}, 'itemProp.length must correctly reflect the number of tokens');
test(function () {
        assert_equals( makeEl('div',{itemprop:'foo FOO'}).itemProp.item(0), 'foo' );
}, 'itemProp.item(0) must return the first token');
test(function () {
        var elem = makeEl('div',{itemprop:'foo'});
        elem.itemProp.add('FOO');
        assert_equals( elem.itemProp.item(1), 'FOO' );
}, 'itemProp.item must return case-sensitive strings and preserve token order');
test(function () {
        assert_equals( makeEl('div',{itemprop:'foo FOO'}).itemProp[0], 'foo' );
}, 'itemProp[0] must return the first token');
test(function () {
        assert_equals( makeEl('div',{itemprop:'foo FOO'}).itemProp[1], 'FOO' );
}, 'itemProp[index] must return case-sensitive strings and preserve token order');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemprop:'foo FOO'}).itemProp[2], window.undefined );
}, 'itemProp[index] must still be undefined for out-of-range index when earlier indexes exist');
test(function () {
        var elem = makeEl('div',{});
        elem.itemProp.add('foo');
        elem.itemProp.add('FOO');
        assert_equals( elem.getAttribute('itemprop'), 'foo FOO' );
}, 'itemprop attribute must update correctly when items have been added through itemProp');
test(function () {
        var elem = makeEl('div',{});
        elem.itemProp.add('foo');
        elem.itemProp.add('FOO');
        assert_equals( elem.itemProp + '', 'foo FOO' );
}, 'itemProp must stringify correctly when items have been added');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.add('foo');
        assert_equals( elem.itemProp.length, 2 );
        assert_equals( elem.itemProp + '', 'foo FOO' );
}, 'itemProp.add must not make any changes if an existing token is added');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.remove('bar');
        assert_equals( elem.itemProp.length, 2 );
        assert_equals( elem.itemProp + '', 'foo FOO' );
}, 'itemProp.remove must not make any changes if a non-existing token is removed');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.remove('foo');
        assert_equals( elem.itemProp.length, 1 );
        assert_equals( elem.itemProp.toString(), 'FOO' );
        assert_false( elem.itemProp.contains('foo') );
        assert_true( elem.itemProp.contains('FOO') );
}, 'itemProp.remove must remove existing tokens');
test(function () {
        var elem = makeEl('div',{itemprop:'test test'});
        elem.itemProp.remove('test');
        assert_equals( elem.itemProp.length, 0 );
        assert_false( elem.itemProp.contains('test') );
}, 'itemProp.remove must remove duplicated tokens');
test(function () {
        var elem = makeEl('div',{itemprop:'token1 token2 token3'});
        elem.itemProp.remove('token2');
        assert_equals( elem.itemProp.toString(), 'token1 token3' );
}, 'itemProp.remove must collapse whitespace around removed tokens');
test(function () {
        var elem = makeEl('div',{itemprop:'  token1  token2  token3  '});
        elem.itemProp.remove('token2');
        assert_equals( elem.itemProp.toString(), 'token1 token3' );
}, 'itemProp.remove must remove all useless whitespace');
test(function () {
        var elem = makeEl('div',{itemprop:'  token1  token2  token3  '});
        elem.itemProp.remove('token1', 'token3');
        assert_equals( elem.itemProp.toString(), 'token2' );
}, 'itemProp.remove must remove useless whitespace when removing multiple tokens');
test(function () {
        var elem = makeEl('div',{itemprop:'  token1  token1  '});
        elem.itemProp.add('token1');
        assert_equals( elem.itemProp.toString(), 'token1' );
}, 'itemProp.add must remove useless whitespace and duplicates when the token already exists');
test(function () {
        var elem = makeEl('div',{itemprop:'FOO'});
        assert_true(elem.itemProp.toggle('foo'));
        assert_equals( elem.itemProp.length, 2 );
        assert_true( elem.itemProp.contains('foo') );
        assert_true( elem.itemProp.contains('FOO') );
}, 'itemProp.toggle must toggle tokens case-sensitively when adding');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        assert_false(elem.itemProp.toggle('foo'));
        assert_false(elem.itemProp.toggle('FOO'));
        assert_false( elem.itemProp.contains('foo') );
        assert_false( elem.itemProp.contains('FOO') );
}, 'itemProp.toggle must be able to remove tokens case-sensitively');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.toggle('foo');
        elem.itemProp.toggle('FOO');
        assert_equals( elem.getAttribute('itemprop'), '' );
}, 'itemprop attribute must be empty when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.toggle('foo');
        elem.itemProp.toggle('FOO');
        assert_equals( elem.itemProp.toString(), '' );
}, 'itemProp must stringify to an empty string when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.toggle('foo');
        elem.itemProp.toggle('FOO');
        assert_equals( elem.itemProp.item(0), null );
}, 'itemProp.item(0) must return null when all classes have been removed');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        var elem = makeEl('div',{itemprop:'foo FOO'});
        elem.itemProp.toggle('foo');
        elem.itemProp.toggle('FOO');
        assert_equals( elem.itemProp[0], window.undefined );
}, 'itemProp[0] must be undefined when all classes have been removed');
//if the last character of DOMTokenSting underlying character is not a space character, append U+0020", where "space character" is from " \t\r\n\f"
test(function () {
        var elem = makeEl('div',{itemprop:'a '});
        elem.itemProp.add('b');
        assert_equals(elem.itemProp.toString(),'a b');
}, 'itemProp.add should treat " " as a space');
test(function () {
        var elem = makeEl('div',{itemprop:'a\t'});
        elem.itemProp.add('b');
        assert_equals(elem.itemProp.toString(),'a b');
}, 'itemProp.add should normalize \\t as a space');
test(function () {
        var elem = makeEl('div',{itemprop:'a\r'});
        elem.itemProp.add('b');
        assert_equals(elem.itemProp.toString(),'a b');
}, 'itemProp.add should normalize \\r as a space');
test(function () {
        var elem = makeEl('div',{itemprop:'a\n'});
        elem.itemProp.add('b');
        assert_equals(elem.itemProp.toString(),'a b');
}, 'itemProp.add should normalize \\n as a space');
test(function () {
        var elem = makeEl('div',{itemprop:'a\f'});
        elem.itemProp.add('b');
        assert_equals(elem.itemProp.toString(),'a b');
}, 'itemProp.add should normalize \\f as a space');
test(function () {
        var elem = makeEl('div',{itemprop:'foo'});
        elem.itemProp.remove('foo');
        elem.removeAttribute('itemprop');
        assert_true( elem.itemProp.toggle('foo') );
}, 'itemProp.toggle must work after removing the itemprop attribute');
test(function () {
        //WebIDL and ECMAScript 5 - a readonly property has a getter but not a setter
        //ES5 makes [[Put]] fail but not throw
        var failed = false;
        var elem = makeEl('div',{itemprop:'token1'});
        try {
                elem.itemProp.length = 0;
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemProp.length,1);
        assert_false(failed,'an error was thrown');
}, 'itemProp.length must be read-only');
test(function () {
        var failed = false, elem = makeEl('div',{itemprop:'test'}), realList = elem.itemProp;
        try {
                elem.itemProp = 'dummy';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemProp,realList);
        assert_equals(elem.itemProp.toString(),'dummy','attempting to write should modify the underlying string');
        assert_false(failed,'an error was thrown');
}, 'itemProp must be read-only');

/* itemId property tests */
test(function () {
        assert_equals( makeEl('div',{itemid:urls.www1 + 'foo'}).itemId, urls.www1 + 'foo' );
        assert_equals( makeEl('div',{itemid:urls.www1 + 'FOO'}).itemId, urls.www1 + 'FOO', 'case-sensitive' );
        assert_equals( makeEl('div',{itemid:' ' + urls.www1 + 'foo '}).itemId, urls.www1 + 'foo', 'whitespace' );
        assert_equals( makeEl('div',{itemid:'data:text/plain,'}).itemId, 'data:text/plain,' );
        assert_equals( makeEl('div',{itemid:'madeup:onthespot'}).itemId, 'madeup:onthespot' );
        assert_equals( makeEl('div',{}).itemId, '' );
}, 'the itemid attribute must be reflected by the .itemId property');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemId = urls.www1 + 'foo';
        assert_equals(testEl.itemId,urls.www1 + 'foo','writing a URL');
        testEl.itemId = '';
        assert_equals(testEl.itemId,location.href,'writing an empty string');
}, 'the itemId property must be read/write');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemId = urls.www1 + 'foo';
        assert_true(testEl.hasAttribute('itemid'),'writing a URL');
        assert_equals(testEl.getAttribute('itemid'),urls.www1 + 'foo','writing a URL');
        testEl = makeEl('div',{})
        testEl.itemId = '';
        assert_true(testEl.hasAttribute('itemid'),'writing an empty string');
        assert_equals(testEl.getAttribute('itemid'),'','writing an empty string');
}, 'writing to the itemId property must create the itemid content attribute');
test(function () {
        assert_equals( makeEl('div',{itemid:'foo'}).itemId, location.href.replace(/\/[^\/]*$/,'\/foo'),'foo' );
        assert_equals( makeEl('div',{itemid:'foo bar'}).itemId, location.href.replace(/\/[^\/]*$/,'\/foo%20bar'),'foo bar' );
        assert_equals( makeEl('div',{itemid:'foo\u0129 bar'}).itemId, location.href.replace(/\/[^\/]*$/,'\/foo%C4%A9%20bar'),'foo\u0129 bar' );
}, 'the itemId property must see the resolved itemid URL');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemId = 'foo';
        assert_equals( testEl.itemId, location.href.replace(/\/[^\/]*$/,'\/foo') );
}, 'the itemId property must see the resolved itemId property URL on setting');
test(function () {
        var testEl = makeEl('div',{});
        testEl.itemId = 'foo';
        assert_equals( testEl.getAttribute('itemid'), 'foo' );
}, 'the itemid attribute must see the resolved itemId URL');

/* itemRef property tests (properties collection tests are done later) */
test(function () {
        assert_equals(makeEl('div',{}).itemRef.toString(),'','no attribute');
        assert_equals(makeEl('div',{itemref:' foo  bar '}).itemRef.toString(),' foo  bar ','with simple tokens');
        var testEl = makeEl('div',{itemref:'foo'});
        testEl.removeAttribute('itemref');
        assert_equals(testEl.itemRef.toString(),'','removed attribute');
}, 'the itemref attribute must be reflected by the .itemRef property');
test(function () {
        assert_equals( typeof makeEl('div',{}).itemRef, 'object' );
}, 'the itemRef property must be an object');
test(function () {
        assert_true( makeEl('div',{}).itemRef instanceof DOMTokenList, 'instanceof test' );
        DOMTokenList.prototype.customProperty = true;
        assert_true( makeEl('div',{}).itemRef.customProperty, 'inheritance test' );
}, 'the itemRef property must implement DOMTokenList');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.itemRef, testEl.itemRef );
}, 'the itemRef property must always reference the same object');
test(function () {
        assert_equals( makeEl('div',{itemref:'test test'}).itemRef.length, 2, 'duplicates in initial string should be preserved' );
        assert_equals( makeEl('div',{itemref:'test test'}).itemRef.item(0), 'test' );
        assert_true( makeEl('div',{itemref:'test test'}).itemRef.contains('test') );
}, 'itemRef must be correct for an element that has itemref tokens');
test(function () {
        assert_equals( makeEl('div',{itemref:' '}).itemRef.length, 0 );
}, 'itemRef.length must be 0 for an element that has no tokens');
test(function () {
        assert_false( makeEl('div',{itemref:' '}).itemRef.contains('foo') );
}, 'itemRef must not contain an undefined class');
test(function () {
        assert_equals( makeEl('div',{itemref:' '}).itemRef.item(0), null );
}, 'itemRef.item() must return null for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemref:' '}).itemRef.item(-1), null );
}, 'itemRef.item() must return null for negative index');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemref:' '}).itemRef[0], window.undefined );
}, 'itemRef[index] must be undefined for out-of-range index');
test(function () {
        assert_equals( makeEl('div',{itemref:' '}).itemRef[-1], window.undefined );
}, 'itemRef[index] must be undefined for negative index');
test(function () {
        assert_equals( makeEl('div',{itemref:' '}).itemRef.toString(), ' ' );
}, 'empty itemRef should stringify to contain the attribute\'s whitespace');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemref:' '}).itemRef.contains(''); } );
}, 'itemRef.contains(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemref:' '}).itemRef.add(''); } );
}, 'itemRef.add(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemref:' '}).itemRef.remove(''); } );
}, 'itemRef.remove(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'SYNTAX_ERR', function () { makeEl('div',{itemref:' '}).itemRef.toggle(''); } );
}, 'itemRef.toggle(empty_string) must throw a SYNTAX_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemref:' '}).itemRef.contains('a b'); } );
}, 'itemRef.contains(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemref:' '}).itemRef.add('a b'); } );
}, 'itemRef.add(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemref:' '}).itemRef.remove('a b'); } );
}, 'itemRef.remove(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        assert_throws( 'INVALID_CHARACTER_ERR', function () { makeEl('div',{itemref:' '}).itemRef.toggle('a b'); } );
}, 'itemRef.toggle(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
        var testEl = makeEl('div',{itemref:'foo'});
        assert_true( testEl.itemRef.contains('foo'), 'before change' );
        testEl.setAttribute('itemref','bar');
        assert_true( testEl.itemRef.contains('bar'), 'after change' );
        assert_false( testEl.itemRef.contains('foo'), 'after change' );
}, 'itemRef.contains must update when the underlying attribute is changed');
test(function () {
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('FOO') );
}, 'itemRef.contains must be case sensitive');
test(function () {
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo.') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo)') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo\'') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo$') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo~') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo?') );
        assert_false( makeEl('div',{itemref:'foo'}).itemRef.contains('foo\\') );
}, 'itemRef.contains must not match when punctuation characters are added');
test(function () {
        var elem = makeEl('div',{itemref:'foo'});
        elem.itemRef.add('FOO');
        assert_true( elem.itemRef.contains('foo') );
}, 'itemRef.add must not remove existing tokens');
test(function () {
        assert_true( makeEl('div',{itemref:'foo FOO'}).itemRef.contains('FOO') );
}, 'itemRef.contains case sensitivity must match a case-specific string');
test(function () {
        assert_equals( makeEl('div',{itemref:'foo FOO'}).itemRef.length, 2 );
}, 'itemRef.length must correctly reflect the number of tokens');
test(function () {
        assert_equals( makeEl('div',{itemref:'foo FOO'}).itemRef.item(0), 'foo' );
}, 'itemRef.item(0) must return the first token');
test(function () {
        var elem = makeEl('div',{itemref:'foo'});
        elem.itemRef.add('FOO');
        assert_equals( elem.itemRef.item(1), 'FOO' );
}, 'itemRef.item must return case-sensitive strings and preserve token order');
test(function () {
        assert_equals( makeEl('div',{itemref:'foo FOO'}).itemRef[0], 'foo' );
}, 'itemRef[0] must return the first token');
test(function () {
        assert_equals( makeEl('div',{itemref:'foo FOO'}).itemRef[1], 'FOO' );
}, 'itemRef[index] must return case-sensitive strings and preserve token order');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        assert_equals( makeEl('div',{itemref:'foo FOO'}).itemRef[2], window.undefined );
}, 'itemRef[index] must still be undefined for out-of-range index when earlier indexes exist');
test(function () {
        var elem = makeEl('div',{});
        elem.itemRef.add('foo');
        elem.itemRef.add('FOO');
        assert_equals( elem.getAttribute('itemref'), 'foo FOO' );
}, 'itemref attribute must update correctly when items have been added through itemRef');
test(function () {
        var elem = makeEl('div',{});
        elem.itemRef.add('foo');
        elem.itemRef.add('FOO');
        assert_equals( elem.itemRef + '', 'foo FOO' );
}, 'itemRef must stringify correctly when items have been added');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.add('foo');
        assert_equals( elem.itemRef.length, 2 );
        assert_equals( elem.itemRef + '', 'foo FOO' );
}, 'itemRef.add must not make any changes if an existing token is added');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.remove('bar');
        assert_equals( elem.itemRef.length, 2 );
        assert_equals( elem.itemRef + '', 'foo FOO' );
}, 'itemRef.remove must not make any changes if a non-existing token is removed');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.remove('foo');
        assert_equals( elem.itemRef.length, 1 );
        assert_equals( elem.itemRef.toString(), 'FOO' );
        assert_false( elem.itemRef.contains('foo') );
        assert_true( elem.itemRef.contains('FOO') );
}, 'itemRef.remove must remove existing tokens');
test(function () {
        var elem = makeEl('div',{itemref:'test test'});
        elem.itemRef.remove('test');
        assert_equals( elem.itemRef.length, 0 );
        assert_false( elem.itemRef.contains('test') );
}, 'itemRef.remove must remove duplicated tokens');
test(function () {
        var elem = makeEl('div',{itemref:'token1 token2 token3'});
        elem.itemRef.remove('token2');
        assert_equals( elem.itemRef.toString(), 'token1 token3' );
}, 'itemRef.remove must collapse whitespace around removed tokens');
test(function () {
        var elem = makeEl('div',{itemref:' token1 token2  '});
        elem.itemRef.remove('token2');
        assert_equals( elem.itemRef.toString(), 'token1' );
}, 'itemRef.remove must remove useless whitespace when removing tokens');
test(function () {
        var elem = makeEl('div',{itemref:'  token1  token2  token3  '});
        elem.itemRef.remove('token2');
        assert_equals( elem.itemRef.toString(), 'token1 token3' );
}, 'itemRef.remove must remove useless whitespace when removing tokens (2)');
test(function () {
        var elem = makeEl('div',{itemref:'  token1  token2  token3  '});
        elem.itemRef.remove('token1', 'token3');
        assert_equals( elem.itemRef.toString(), 'token2' );
}, 'itemRef.remove must collapse whitespace when removing multiple tokens');
test(function () {
        var elem = makeEl('div',{itemref:'  token1  token1  '});
        elem.itemRef.add('token1');
        assert_equals( elem.itemRef.toString(), 'token1' );
}, 'itemRef.add must remove whitespace and duplicate when the token already exists');
test(function () {
        var elem = makeEl('div',{itemref:'FOO'});
        assert_true(elem.itemRef.toggle('foo'));
        assert_equals( elem.itemRef.length, 2 );
        assert_true( elem.itemRef.contains('foo') );
        assert_true( elem.itemRef.contains('FOO') );
}, 'itemRef.toggle must toggle tokens case-sensitively when adding');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        assert_false(elem.itemRef.toggle('foo'));
        assert_false(elem.itemRef.toggle('FOO'));
        assert_false( elem.itemRef.contains('foo') );
        assert_false( elem.itemRef.contains('FOO') );
}, 'itemRef.toggle must be able to remove tokens case-sensitively');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.toggle('foo');
        elem.itemRef.toggle('FOO');
        assert_equals( elem.getAttribute('itemref'), '' );
}, 'itemref attribute must be empty when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.toggle('foo');
        elem.itemRef.toggle('FOO');
        assert_equals( elem.itemRef.toString(), '' );
}, 'itemRef must stringify to an empty string when all classes have been removed');
test(function () {
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.toggle('foo');
        elem.itemRef.toggle('FOO');
        assert_equals( elem.itemRef.item(0), null );
}, 'itemRef.item(0) must return null when all classes have been removed');
test(function () {
        /* the normative part of the spec states that:
        "unless the length is zero, in which case there are no supported property indices"
        ...
        "The term[...] supported property indices [is] used as defined in the WebIDL specification."
        WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
        var elem = makeEl('div',{itemref:'foo FOO'});
        elem.itemRef.toggle('foo');
        elem.itemRef.toggle('FOO');
        assert_equals( elem.itemRef[0], window.undefined );
}, 'itemRef[0] must be undefined when all classes have been removed');
//if the last character of DOMTokenSting underlying character is not a space character, append U+0020", where "space character" is from " \t\r\n\f"
test(function () {
        var elem = makeEl('div',{itemref:'a '});
        elem.itemRef.add('b');
        assert_equals(elem.itemRef.toString(),'a b');
}, 'itemRef.add should treat " " as a space');
test(function () {
        var elem = makeEl('div',{itemref:'a\t'});
        elem.itemRef.add('b');
        assert_equals(elem.itemRef.toString(),'a b');
}, 'itemRef.add should normalize \\t as a space');
test(function () {
        var elem = makeEl('div',{itemref:'a\r'});
        elem.itemRef.add('b');
        assert_equals(elem.itemRef.toString(),'a b');
}, 'itemRef.add should normalize \\r as a space');
test(function () {
        var elem = makeEl('div',{itemref:'a\n'});
        elem.itemRef.add('b');
        assert_equals(elem.itemRef.toString(),'a b');
}, 'itemRef.add should normalize \\n as a space');
test(function () {
        var elem = makeEl('div',{itemref:'a\f'});
        elem.itemRef.add('b');
        assert_equals(elem.itemRef.toString(),'a b');
}, 'itemRef.add should normalize \\f as a space');
test(function () {
        var elem = makeEl('div',{itemref:'foo'});
        elem.itemRef.remove('foo');
        elem.removeAttribute('itemref');
        assert_true( elem.itemRef.toggle('foo') );
}, 'itemRef.toggle must work after removing the itemref attribute');
test(function () {
        //WebIDL and ECMAScript 5 - a readonly property has a getter but not a setter
        //ES5 makes [[Put]] fail but not throw
        var failed = false;
        var elem = makeEl('div',{itemref:'token1'});
        try {
                elem.itemRef.length = 0;
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemRef.length,1);
        assert_false(failed,'an error was thrown');
}, 'itemRef.length must be read-only');
test(function () {
        var failed = false, elem = makeEl('div',{itemref:'test'}), realList = elem.itemRef;
        try {
                elem.itemRef = 'dummy';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.itemRef,realList);
        assert_equals(elem.itemRef.toString(),'dummy','attempting to write should modify the underlying string');
        assert_false(failed,'an error was thrown');
}, 'itemRef must be read-only');

/* itemValue property tests */
test(function () {
        assert_equals( makeEl('meta',{content:'test'}).itemValue, null, 'meta' );
        assert_equals( makeEl('audio',{src:'test'}).itemValue, null, 'audio' );
        assert_equals( makeEl('embed',{src:'test'}).itemValue, null, 'embed' );
        assert_equals( makeEl('iframe',{src:'test'}).itemValue, null, 'iframe' );
        assert_equals( makeEl('img',{src:'test'}).itemValue, null, 'img' );
        assert_equals( makeEl('source',{src:'test'}).itemValue, null, 'source' );
        assert_equals( makeEl('track',{src:'test'}).itemValue, null, 'track' );
        assert_equals( makeEl('video',{src:'test'}).itemValue, null, 'video' );
        assert_equals( makeEl('a',{href:'test'}).itemValue, null, 'a' );
        assert_equals( makeEl('area',{href:'test'}).itemValue, null, 'area' );
        assert_equals( makeEl('link',{href:'test'}).itemValue, null, 'link' );
        assert_equals( makeEl('object',{data:'test'}).itemValue, null, 'object' );
        assert_equals( makeEl('time',{}).itemValue, null, 'time without datetime' );
        assert_equals( makeEl('time',{datetime:'test'}).itemValue, null, 'time with datetime' );
        assert_equals( makeEl('div',{},'test').itemValue, null, 'otherwise' );
        assert_equals( makeEl('madeuponthespot',{},'test').itemValue, null, 'unknown element' );
}, 'itemValue must be null if the element does not have an itemprop attribute');
test(function () {
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('meta',{content:'test'}); testEl.itemValue = 'test2'; }, 'meta' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('audio',{content:'test'}); testEl.itemValue = 'test2'; }, 'audio' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('embed',{content:'test'}); testEl.itemValue = 'test2'; }, 'embed' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('iframe',{content:'test'}); testEl.itemValue = 'test2'; }, 'iframe' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('img',{content:'test'}); testEl.itemValue = 'test2'; }, 'img' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('source',{content:'test'}); testEl.itemValue = 'test2'; }, 'source' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('track',{content:'test'}); testEl.itemValue = 'test2'; }, 'track' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('video',{content:'test'}); testEl.itemValue = 'test2'; }, 'video' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('a',{href:'test'}); testEl.itemValue = 'test2'; }, 'a' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('area',{href:'test'}); testEl.itemValue = 'test2'; }, 'area' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('link',{href:'test'}); testEl.itemValue = 'test2'; }, 'link' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('object',{data:'test'}); testEl.itemValue = 'test2'; }, 'object' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('time',{}); testEl.itemValue = 'test2'; }, 'time without datetime' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('time',{datetime:'test'}); testEl.itemValue = 'test2'; }, 'time with datetime' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('div',{},'test'); testEl.itemValue = 'test2'; }, 'otherwise' );
        assert_throws( 'INVALID_ACCESS_ERR', function () { var testEl = makeEl('madeuponthespot',{},'test'); testEl.itemValue = 'test2'; }, 'unknown element' );
}, 'writing to itemValue must throw an INVALID_ACCESS_ERR error if the element does not have an itemprop attribute');
test(function () {
        var testEl;
        testEl = makeEl('meta',{itemscope:'itemscope',itemprop:'foo',content:'test'});
        assert_equals( testEl.itemValue, testEl, 'meta' );
        testEl = makeEl('audio',{itemscope:'itemscope',itemprop:'foo',src:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'audio' );
        testEl = makeEl('embed',{itemscope:'itemscope',itemprop:'foo',src:'test'});
        assert_equals( testEl.itemValue, testEl, 'embed' );
        testEl = makeEl('iframe',{itemscope:'itemscope',itemprop:'foo',src:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'iframe' );
        testEl = makeEl('img',{itemscope:'itemscope',itemprop:'foo',src:'test'});
        assert_equals( testEl.itemValue, testEl, 'img' );
        testEl = makeEl('source',{itemscope:'itemscope',itemprop:'foo',src:'test'});
        assert_equals( testEl.itemValue, testEl, 'source' );
        testEl = makeEl('track',{itemscope:'itemscope',itemprop:'foo',src:'test'});
        assert_equals( testEl.itemValue, testEl, 'track' );
        testEl = makeEl('video',{itemscope:'itemscope',itemprop:'foo',src:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'video' );
        testEl = makeEl('a',{itemscope:'itemscope',itemprop:'foo',href:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'a' );
        testEl = makeEl('area',{itemscope:'itemscope',itemprop:'foo',href:'test'});
        assert_equals( testEl.itemValue, testEl, 'area' );
        testEl = makeEl('link',{itemscope:'itemscope',itemprop:'foo',href:'test'});
        assert_equals( testEl.itemValue, testEl, 'link' );
        testEl = makeEl('object',{itemscope:'itemscope',itemprop:'foo',data:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'object' );
        testEl = makeEl('time',{itemscope:'itemscope',itemprop:'foo'},'fail');
        assert_equals( testEl.itemValue, testEl, 'time without datetime' );
        testEl = makeEl('time',{itemscope:'itemscope',itemprop:'foo',datetime:'test'},'fail');
        assert_equals( testEl.itemValue, testEl, 'time with datetime' );
        testEl = makeEl('div',{itemscope:'itemscope',itemprop:'foo'},'test');
        assert_equals( testEl.itemValue, testEl, 'otherwise' );
        testEl = makeEl('madeuponthespot',{itemscope:'itemscope',itemprop:'foo'},'test');
        assert_equals( testEl.itemValue, testEl, 'unknown element' );
        testEl = makeEl('madeuponthespot',{itemscope:'itemscope',itemprop:'foo',content:'test',src:'test',href:'test',data:'test',datetime:'test',value:'test'},'test');
        assert_equals( testEl.itemValue, testEl, 'unknown element with known attributes' );
        testEl = makeEl('input',{itemscope:'itemscope',itemprop:'foo',value:'test'},'test');
        assert_equals( testEl.itemValue, testEl, 'input' );
}, 'itemValue must return the element if the element has an itemscope attribute');
test(function () {
        var testEl = makeEl('meta',{itemprop:'foo',content:'test'});
        assert_equals( testEl.itemValue, 'test', 'reading' );
        testEl.content = 'retest';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = 'bar';
        assert_equals( testEl.content, 'bar', 'writing (checking content)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
}, 'itemValue must reflect the content attribute on meta elements');
test(function () {
        var testEl = makeEl('audio',{itemprop:'foo',src:urls.base},'contained text');
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, 'contained text', 'writing (checking textContent)' );
        assert_equals( makeEl('audio',{itemprop:'foo'},'contained text').itemValue, '', 'reading with missing attribute' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on audio elements');
test(function () {
        var testEl = makeEl('embed',{itemprop:'foo',src:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on embed elements');
test(function () {
        var testEl = makeEl('iframe',{itemprop:'foo',src:urls.base},'contained text');
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, 'contained text', 'writing (checking textContent)' );
        assert_equals( makeEl('iframe',{itemprop:'foo'},'contained text').itemValue, '', 'reading with missing attribute' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on iframe elements');
test(function () {
        var testEl = makeEl('img',{itemprop:'foo',src:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on img elements');
test(function () {
        var testEl = makeEl('source',{itemprop:'foo',src:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on source elements');
test(function () {
        var testEl = makeEl('track',{itemprop:'foo',src:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on track elements');
test(function () {
        var testEl = makeEl('video',{itemprop:'foo',src:urls.base},'contained text');
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.src = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.src, urls.www1, 'writing (checking src)' );
        assert_equals( testEl.textContent, 'contained text', 'writing (checking textContent)' );
        assert_equals( makeEl('video',{itemprop:'foo'},'contained text').itemValue, '', 'reading with missing attribute' );
        testEl.src = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on video elements');
test(function () {
        var testEl = makeEl('a',{itemprop:'foo',href:urls.base},'contained text');
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.href = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.href, urls.www1, 'writing (checking href)' );
        assert_equals( testEl.textContent, 'contained text', 'writing (checking textContent)' );
        assert_equals( makeEl('a',{itemprop:'foo'},'contained text').itemValue, '', 'reading with missing attribute' );
        testEl.href = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on anchor elements');
test(function () {
        var testEl = makeEl('area',{itemprop:'foo',href:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.href = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.href, urls.www1, 'writing (checking href)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.href = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on area elements');
test(function () {
        var testEl = makeEl('link',{itemprop:'foo',href:urls.base});
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.href = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.href, urls.www1, 'writing (checking href)' );
        assert_equals( testEl.textContent, '', 'writing (checking textContent)' );
        testEl.href = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on link elements');
test(function () {
        var testEl = makeEl('object',{itemprop:'foo',data:urls.base},'contained text');
        assert_equals( testEl.itemValue, urls.base, 'reading' );
        testEl.data = urls.www;
        assert_equals( testEl.itemValue, urls.www, 'reading after change' );
        testEl.itemValue = urls.www1;
        assert_equals( testEl.data, urls.www1, 'writing (checking data)' );
        assert_equals( testEl.textContent, 'contained text', 'writing (checking textContent)' );
        assert_equals( makeEl('object',{itemprop:'foo'},'contained text').itemValue, '', 'reading with missing attribute' );
        testEl.data = 'bar';
        assert_equals( testEl.itemValue, location.href.replace(/\/[^\/]*$/,'/bar'), 'resolving URLs' );
}, 'itemValue must reflect the src attribute on object elements');
test(function () {
        var testEl = makeEl('time',{itemprop:'foo'},'te <span itemprop="bar" itemscope>st</span> ing');
        assert_equals( testEl.itemValue, 'te st ing', 'reading' );
        testEl.innerHTML = 'retest';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = '2001-02-03T04:05:06Z';
        assert_equals( testEl.dateTime, '2001-02-03T04:05:06Z', 'writing (checking dateTime)' );
        assert_equals( testEl.textContent, 'retest', 'writing (checking textContent)' );
        assert_equals( testEl.itemValue, '2001-02-03T04:05:06Z', 'writing (checking itemValue)' );
}, 'itemValue must reflect the dateTime attribute of time elements with no datetime attribute');
test(function () {
        var testEl = makeEl('time',{itemprop:'foo',datetime:'test'},'te <span itemprop="bar" itemscope>st</span> ing');
        assert_equals( testEl.itemValue, 'test', 'reading' );
        testEl.dateTime = 'retest';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = '2001-02-03T04:05:06Z';
        assert_equals( testEl.dateTime, '2001-02-03T04:05:06Z', 'writing (checking dateTime)' );
        assert_equals( testEl.textContent, 'te st ing', 'writing (checking textContent)' );
}, 'itemValue must reflect the datetime attribute of time elements with a datetime attribute');
test(function () {
        var testEl = makeEl('div',{itemprop:'foo'},'te <span itemprop="bar" itemscope>st</span> ing');
        assert_equals( testEl.itemValue, 'te st ing', 'reading' );
        testEl.innerHTML = 're<strong>te</strong>st';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = 'test';
        assert_equals( testEl.textContent, 'test', 'writing' );
}, 'itemValue must reflect the textContent of other elements');
test(function () {
        var testEl = makeEl('madeuponthespot',{itemprop:'foo'},'te <span itemprop="bar" itemscope>st</span> ing');
        assert_equals( testEl.itemValue, 'te st ing', 'reading' );
        testEl.innerHTML = 're<strong>te</strong>st';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = 'test';
        assert_equals( testEl.textContent, 'test', 'writing' );
}, 'itemValue must reflect the textContent of unknown elements');
test(function () {
        var testEl = makeEl('madeuponthespot',{itemprop:'foo',content:'test',src:'test',href:'test',data:'test',datetime:'test',value:'test'},'te <span itemprop="bar" itemscope>st</span> ing');
        assert_equals( testEl.itemValue, 'te st ing', 'reading' );
        testEl.innerHTML = 're<strong>te</strong>st';
        assert_equals( testEl.itemValue, 'retest', 'reading after change' );
        testEl.itemValue = 'test';
        assert_equals( testEl.textContent, 'test', 'writing' );
}, 'itemValue must reflect the textContent of unknown elements with known attributes');
test(function () {
        var testEl = makeEl('input',{itemprop:'foo',value:'test'});
        assert_equals( testEl.itemValue, '', 'reading' );
        testEl.value = 'retest';
        assert_equals( testEl.itemValue, '', 'reading after change' );
}, 'itemValue must not reflect the value of input elements');
test(function () {
        var testEl, eltypes = [
                makeEl('meta',{itemprop:'foo',content:'test'}),
                makeEl('audio',{itemprop:'foo',src:'test'},'fail'),
                makeEl('embed',{itemprop:'foo',src:'test'}),
                makeEl('iframe',{itemprop:'foo',src:'test'},'fail'),
                makeEl('img',{itemprop:'foo',src:'test'}),
                makeEl('source',{itemprop:'foo',src:'test'}),
                makeEl('track',{itemprop:'foo',src:'test'}),
                makeEl('video',{itemprop:'foo',src:'test'},'fail'),
                makeEl('a',{itemprop:'foo',href:'test'},'fail'),
                makeEl('area',{itemprop:'foo',href:'test'}),
                makeEl('link',{itemprop:'foo',href:'test'}),
                makeEl('object',{itemprop:'foo',data:'test'},'fail'),
                makeEl('time',{itemprop:'foo'},'fail'),
                makeEl('time',{itemprop:'foo',datetime:'test'},'fail'),
                makeEl('div',{itemprop:'foo'},'test'),
                makeEl('madeuponthespot',{itemprop:'foo'},'test'),
                makeEl('madeuponthespot',{itemprop:'foo',content:'test',src:'test',href:'test',data:'test',datetime:'test',value:'test'},'test'),
                makeEl('input',{itemprop:'foo',value:'test'},'test')
        ], beforeValues, i;
        for( i = 0; i < eltypes.length; i++ ) {
                testEl = eltypes[i];
                beforeValues = testEl.itemValue;
                testEl.itemScope = true;
                assert_equals( testEl.itemValue, testEl, 'itemscope enabled on '+testEl.tagName+' index '+i );
                testEl.itemScope = false;
                assert_equals( testEl.itemValue, beforeValues, 'itemscope disabled on '+testEl.tagName+' index '+i );
                testEl.itemScope = true;
                testEl.removeAttribute('itemscope');
                assert_equals( testEl.itemValue, beforeValues, 'itemscope attribute removed on '+testEl.tagName+' index '+i );
        }
}, 'dynamic changes of itemscope should change the value exposed through itemValue');

test(function () {
        var testEl, eltypes = [
                makeEl('meta',{itemprop:'foo',content:'test'}),
                makeEl('audio',{itemprop:'foo',src:'test'},'fail'),
                makeEl('embed',{itemprop:'foo',src:'test'}),
                makeEl('iframe',{itemprop:'foo',src:'test'},'fail'),
                makeEl('img',{itemprop:'foo',src:'test'}),
                makeEl('source',{itemprop:'foo',src:'test'}),
                makeEl('track',{itemprop:'foo',src:'test'}),
                makeEl('video',{itemprop:'foo',src:'test'},'fail'),
                makeEl('a',{itemprop:'foo',href:'test'},'fail'),
                makeEl('area',{itemprop:'foo',href:'test'}),
                makeEl('link',{itemprop:'foo',href:'test'}),
                makeEl('object',{itemprop:'foo',data:'test'},'fail'),
                makeEl('time',{itemprop:'foo'},'fail'),
                makeEl('time',{itemprop:'foo',datetime:'test'},'fail'),
                makeEl('div',{itemprop:'foo'},'test'),
                makeEl('madeuponthespot',{itemprop:'foo'},'test'),
                makeEl('madeuponthespot',{itemprop:'foo',content:'test',src:'test',href:'test',data:'test',datetime:'test',value:'test'},'test'),
                makeEl('input',{itemprop:'foo',value:'test'},'test')
        ], beforeValues, i;
        for( i = 0; i < eltypes.length; i++ ) {
                testEl = eltypes[i];
                beforeValues = testEl.itemValue;
                testEl.itemProp.remove('foo');
                assert_equals( testEl.itemValue, beforeValues, 'itemprop tokens removed on '+testEl.tagName+' index '+i );
                testEl.removeAttribute('itemprop');
                assert_equals( testEl.itemValue, null, 'itemprop attribute removed on '+testEl.tagName+' index '+i );
                testEl.itemProp.toggle('foo');
                assert_equals( testEl.itemValue, beforeValues, 'itemprop tokens added on '+testEl.tagName+' index '+i );
        }
}, 'dynamic changes of itemprop should change the value exposed through itemValue');

/* properties */
test(function () {
        assert_equals( typeof makeEl('div',{}).properties, 'object' );
}, 'the properties property must be an object');
test(function () {
        var testEl = makeEl('div',{});
        assert_true( testEl.properties instanceof HTMLPropertiesCollection, 'instanceof HTMLPropertiesCollection' );
        assert_true( testEl.properties instanceof HTMLCollection, 'instanceof HTMLCollection' );
        HTMLPropertiesCollection.prototype.customProperty = true;
        HTMLCollection.prototype.anotherCustomProperty = true;
        assert_true( testEl.properties.customProperty, 'inheritance from HTMLPropertiesCollection' );
        assert_true( testEl.properties.anotherCustomProperty, 'inheritance from HTMLCollection' );
        HTMLPropertiesCollection.prototype.anotherCustomProperty = false;
        assert_false( testEl.properties.anotherCustomProperty, 'shadowing by HTMLPropertiesCollection' );
}, 'the properties property must implement HTMLPropertiesCollection and HTMLCollection');
test(function () {
        var failed = false, elem = makeEl('div',{itemscope:'itemscope'}), realList = elem.properties;
        try {
                elem.properties = '';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.properties,realList);
        assert_false(failed,'an error was thrown');
}, 'the properties property must be read-only');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.properties, testEl.properties );
}, 'the properties property must always reference the same object');
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        assert_equals( testEl.properties.length, 0, 'length' );
        assert_true( !testEl.properties.item(0), 'item(0)' );
        assert_true( !testEl.properties[0], '[0]' );
        assert_equals( testEl.properties.namedItem('foo').length, 0, 'namedItem' );
        assert_true( !testEl.properties['foo'], '[namedItem]' );
        assert_equals( testEl.properties.namedItem('foo').getValues().length, 0, 'namedItem' );
        assert_equals( testEl.properties.names.length, 0, 'names' );
}, 'the properties collection must be empty if the element does not have an itemscope property');
test(function() {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        assert_throws( new TypeError(), function() { testEl.properties('foo'); } );
        assert_throws( new TypeError(), function() { testEl.properties(0); } );
}, 'the properties collection must not support legacycaller');  
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        testEl.itemScope = true;
        assert_equals( testEl.properties.length, 1, 'length' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'item(0)' );
        assert_equals( testEl.properties[0], testEl.firstChild, '[0]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'namedItem' );
        assert_equals( testEl.properties['foo'].length, 1, '[namedItem]' );
        assert_equals( testEl.properties.namedItem('foo').getValues().length, 1, 'namedItem' );
        assert_equals( testEl.properties.names.length, 1, 'names' );
}, 'the properties collection must become populated if the element is given an itemscope property');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo">bar</div>');
        testEl.itemScope = false;
        assert_equals( testEl.properties.length, 0, 'length' );
        assert_true( !testEl.properties.item(0), 'item(0)' );
        assert_true( !testEl.properties[0], '[0]' );
        assert_equals( testEl.properties.namedItem('foo').length, 0, 'namedItem' );
        assert_true( !testEl.properties['foo'], '[namedItem]' );
        assert_equals( testEl.properties.namedItem('foo').getValues().length, 0, 'namedItem' );
        assert_equals( testEl.properties.names.length, 0, 'names' );
}, 'the properties collection must become empty if the element\'s itemscope property is removed');
//properties.item and properties.length (part 1)
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.length, 5 );
}, 'properties.length must be the total number of properties');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.item(0), testEl.childNodes[0], 'item(0)' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'item(1)' );
        assert_equals( testEl.properties.item(2), testEl.childNodes[1].childNodes[0], 'item(2)' );
        assert_equals( testEl.properties.item(3), testEl.childNodes[2], 'item(3)' );
}, 'properties.item must give each property in tree order');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        testEl.properties.something = "another";
        var names = Object.getOwnPropertyNames(testEl.properties);
        assert_array_equals( names, ["0", "1", "2", "3", "foo", "bar", "baz", "qux", "something"] );
}, 'properties.item must have the right property names on it when enumerated');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.item(4), null, 'positive index' );
        assert_equals( testEl.properties.item(-1), null, 'negative index' );
}, 'properties.item must give null for out of range index');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties[0], testEl.childNodes[0], '[0]' );
        assert_equals( testEl.properties[1], testEl.childNodes[1], '[1]' );
        assert_equals( testEl.properties[2], testEl.childNodes[1].childNodes[0], '[2]' );
        assert_equals( testEl.properties[3], testEl.childNodes[2], '[3]' );
}, 'properties[index] must give each property in tree order');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties[4], window.undefined, 'positive index' );
        assert_equals( testEl.properties[-1], window.undefined, 'negative index' );
}, 'properties[index] must give undefined for out of range index');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemscope itemprop="foo"><div itemprop="bar"></div></div><div><div itemprop="baz"></div></div>');
        assert_equals( testEl.properties.length, 2, 'length' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0)' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0]' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1].firstChild, 'properties.item(1)' );
        assert_equals( testEl.properties[1], testEl.childNodes[1].firstChild, 'properties[1]' );
}, 'properties.item and length must ignore properties of nested items');
test(function () {
        //note, itemref ordering is reversed compared with the next test to catch failed sorting algorithms
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id2 id1"><div itemprop="bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        document.body.appendChild(parEl);
        var propLength = testEl.properties.length;
        var item0 = testEl.properties.item(0);
        var square0 = testEl.properties[0];
        var item1 = testEl.properties.item(1);
        var square1 = testEl.properties[1];
        var item2 = testEl.properties.item(2);
        var square2 = testEl.properties[2];
        var item3 = testEl.properties.item(3);
        var square3 = testEl.properties[3];
        document.body.removeChild(parEl);
        assert_equals( propLength, 4, 'length' );
        assert_equals( item0, parEl.firstChild, 'properties.item(0)' );
        assert_equals( square0, parEl.firstChild, 'properties[0]' );
        assert_equals( item1, testEl.firstChild, 'properties.item(1)' );
        assert_equals( square1, testEl.firstChild, 'properties[1]' );
        assert_equals( item2, parEl.childNodes[2], 'properties.item(2)' );
        assert_equals( square2, parEl.childNodes[2], 'properties[2]' );
        assert_equals( item3, parEl.childNodes[2].firstChild, 'properties.item(3)' );
        assert_equals( square3, parEl.childNodes[2].firstChild, 'properties[3]' );
}, 'properties.item and length must see items added with itemref when attached to the document\'s DOM');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id1 id2"><div itemprop="bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 4, 'length' );
        assert_equals( testEl.properties.item(0), parEl.firstChild, 'properties.item(0)' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0]' );
        assert_equals( testEl.properties.item(1), testEl.firstChild, 'properties.item(1)' );
        assert_equals( testEl.properties[1], testEl.firstChild, 'properties[1]' );
        assert_equals( testEl.properties.item(2), parEl.childNodes[2], 'properties.item(2)' );
        assert_equals( testEl.properties[2], parEl.childNodes[2], 'properties[2]' );
        assert_equals( testEl.properties.item(3), parEl.childNodes[2].firstChild, 'properties.item(3)' );
        assert_equals( testEl.properties[3], parEl.childNodes[2].firstChild, 'properties[3]' );
}, 'properties.item and length must see items added with itemref');
test(function () {
        var parEl = makeEl('div',{},'<div itemscope itemref="id1"></div><div itemprop="foo" id="id1"><div itemprop="bar"></div></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[0];
        assert_equals( testEl.properties.length, 2, 'length' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1)' );
        document.body.appendChild(parEl)
        var length = testEl.properties.length;
        var item0 = testEl.properties.item(0);
        var item1 = testEl.properties.item(0);
        document.body.removeChild(parEl)
        assert_equals( testEl.properties.length, 2, 'length (attached to document)' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0) (attached to document)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1) (attached to document)' );
}, 'itemref must reference the first element with a given ID');
test(function () {
        var parEl = makeEl('div',{},'<div itemscope itemref="id1 id1"></div><div itemprop="foo" id="id1"><div itemprop="bar"></div></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[0];
        assert_equals( testEl.properties.length, 2, 'length' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1)' );
        document.body.appendChild(parEl)
        var length = testEl.properties.length;
        var item0 = testEl.properties.item(0);
        var item1 = testEl.properties.item(0);
        document.body.removeChild(parEl)
        assert_equals( testEl.properties.length, 2, 'length (attached to document)' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0) (attached to document)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1) (attached to document)' );
}, 'itemref must ignore duplicated IDs');
test(function () {
        var parEl = makeEl('div',{},'<div itemscope itemref="id0 id1"></div><div itemprop="foo" id="id1"><div itemprop="bar"></div></div>');
        var testEl = parEl.childNodes[0];
        assert_equals( testEl.properties.length, 2, 'length' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1)' );
        document.body.appendChild(parEl)
        var length = testEl.properties.length;
        var item0 = testEl.properties.item(0);
        var item1 = testEl.properties.item(0);
        document.body.removeChild(parEl)
        assert_equals( testEl.properties.length, 2, 'length (attached to document)' );
        assert_equals( testEl.properties.item(0), parEl.childNodes[1], 'properties.item(0) (attached to document)' );
        assert_equals( testEl.properties.item(1), parEl.childNodes[1].firstChild, 'properties.item(1) (attached to document)' );
}, 'itemref must ignore non-existent IDs');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1'});
        var dummyEl = makeEl('div',{id:'id1',itemprop:'foo'});
        assert_equals( testEl.properties.length, 0 );
}, 'itemref in a dislocated tree must not reference elements from another dislocated tree');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1'});
        var dummyEl = makeEl('div',{id:'id1',itemprop:'foo'});
        document.body.appendChild(dummyEl);
        var tmp = testEl.properties.length;
        document.body.removeChild(dummyEl);
        assert_equals( tmp, 0 );
}, 'itemref in a dislocated tree must not reference elements from the main document');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1'});
        var dummyEl = makeEl('div',{id:'id1',itemprop:'foo'});
        document.body.appendChild(testEl);
        var tmp = testEl.properties.length;
        document.body.removeChild(testEl);
        assert_equals( tmp, 0 );
}, 'itemref in the main document must not reference elements from a dislocated tree');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_equals( testEl.properties.length, 1, 'length (before test)' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0) (before test)' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] (before test)' );
        testEl.appendChild(makeEl('div',{itemprop:'bar'}));
        assert_equals( testEl.properties.length, 2, 'length after adding a child' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'properties.item(1) after adding a child' );
        assert_equals( testEl.properties[1], testEl.childNodes[1], 'properties[1] after adding a child' );
        testEl.lastChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( testEl.properties.length, 3, 'length after adding a child with duplicated name' );
        assert_equals( testEl.properties.item(2), testEl.childNodes[1].firstChild, 'properties.item(2) after adding a child with duplicated name' );
        assert_equals( testEl.properties[2], testEl.childNodes[1].firstChild, 'properties[2] after adding a child with duplicated name' );
        testEl.lastChild.removeChild(testEl.lastChild.firstChild);
        assert_equals( testEl.properties.length, 2, 'length after removing a child' );
        assert_true( !testEl.properties.item(2), 'properties.item(1) after removing a child' );
        assert_true( !testEl.properties[2], 'properties[1] after removing a child' );
}, 'properties.item and length must update when adding property elements');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"></div>');
        assert_equals( testEl.properties.length, 2, 'length (before test)' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0) (before test)' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] (before test)' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'properties.item(1) (before test)' );
        assert_equals( testEl.properties[1], testEl.childNodes[1], 'properties[1] (before test)' );
        testEl.appendChild(testEl.firstChild);
        assert_equals( testEl.properties.length, 2, 'length (after test)' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0) (after test)' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] (after test)' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'properties.item(1) (after test)' );
        assert_equals( testEl.properties[1], testEl.childNodes[1], 'properties[1] (after test)' );
}, 'properties.item must update when re-ordering property elements, but length must not');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div></div>');
        assert_equals( testEl.properties.length, 1, 'length (before test)' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] (before test)' );
        testEl.lastChild.itemProp.toggle('bar');
        assert_equals( testEl.properties.length, 2, 'length (after test 1)' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0) after adding a token' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] after adding a token' );
        assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'properties.item(1) after adding a token' );
        assert_equals( testEl.properties[1], testEl.childNodes[1], 'properties[1] after adding a token' );
        testEl.lastChild.removeAttribute('itemprop');
        assert_equals( testEl.properties.length, 1, 'length after removing an attribute' );
        assert_equals( testEl.properties.item(0), testEl.firstChild, 'properties.item(0) after removing an attribute' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] after removing an attribute' );
        assert_true( !testEl.properties.item(1), 'properties.item(1) after removing an attribute' );
        assert_true( !testEl.properties[1], 'properties[1] after removing an attribute' );
}, 'properties.item and length must update when changing itemProp of children');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 0, 'length (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( testEl.properties.length, 2, 'length after id is created' );
        assert_equals( testEl.properties.item(0), parEl.firstChild, 'properties.item(0) after id is created' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] after id is created' );
        assert_equals( testEl.properties.item(1), parEl.firstChild.firstChild, 'properties.item(1) after id is created' );
        assert_equals( testEl.properties[1], parEl.firstChild.firstChild, 'properties[1] after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( testEl.properties.length, 0, 'length after removing an attribute' );
        assert_true( !testEl.properties.item(0), 'properties.item(0) after removing an attribute' );
        assert_true( !testEl.properties[0], 'properties[0] after removing an attribute' );
        document.body.appendChild(parEl);
        var beflength = testEl.properties.length;
        parEl.firstChild.id = 'id1';
        var length1 = testEl.properties.length,
            item0 = testEl.properties.item(0),
            prop0 = testEl.properties[0],
            item1 = testEl.properties.item(1),
            prop1 = testEl.properties[1];
        parEl.firstChild.removeAttribute('id');
        var length2 = testEl.properties.length,
            bitem = !testEl.properties.item(0),
            bprop = !testEl.properties[0];
        document.body.removeChild(parEl);
        assert_equals( beflength, 0, 'length (before test) when appended to document' );
        assert_equals( length1, 2, 'length after id is created when appended to document' );
        assert_equals( item0, parEl.firstChild, 'properties.item(0) after id is created when appended to document' );
        assert_equals( prop0, parEl.firstChild, 'properties[0] after id is created when appended to document' );
        assert_equals( item1, parEl.firstChild.firstChild, 'properties.item(1) after id is created when appended to document' );
        assert_equals( prop1, parEl.firstChild.firstChild, 'properties[1] after id is created when appended to document' );
        assert_equals( length2, 0, 'length after removing an attribute when appended to document' );
        assert_true( bitem, 'properties.item(0) after removing an attribute when appended to document' );
        assert_true( bprop, 'properties[0] after removing an attribute when appended to document' );
}, 'properties.item and length must update when changing id of referenced sibling');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 1, 'length (before test)' );
        assert_equals( testEl.properties.item(0), parEl.lastChild, 'properties.item(0) (before test)' );
        assert_equals( testEl.properties[0], parEl.lastChild, 'properties[0] (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( testEl.properties.length, 2, 'length after id is created' );
        assert_equals( testEl.properties.item(0), parEl.firstChild, 'properties.item(0) after id is created' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] after id is created' );
        assert_equals( testEl.properties.item(1), parEl.firstChild.firstChild, 'properties.item(1) after id is created' );
        assert_equals( testEl.properties[1], parEl.firstChild.firstChild, 'properties[1] after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( testEl.properties.length, 1, 'length after removing an attribute' );
        assert_equals( testEl.properties.item(0), parEl.lastChild, 'properties.item(0) after removing an attribute' );
        assert_equals( testEl.properties[0], parEl.lastChild, 'properties[0] after removing an attribute' );
        document.body.appendChild(parEl);
        var beflength = testEl.properties.length,
            befitem = testEl.properties.item(0),
            befprop = testEl.properties[0];
        parEl.firstChild.id = 'id1';
        var length1 = testEl.properties.length,
            item0 = testEl.properties.item(0),
            prop0 = testEl.properties[0],
            item1 = testEl.properties.item(1),
            prop1 = testEl.properties[1];
        parEl.firstChild.removeAttribute('id');
        var length2 = testEl.properties.length,
            afitem = testEl.properties.item(0),
            afprop = testEl.properties[0];
        document.body.removeChild(parEl);
        assert_equals( beflength, 1, 'length (before test) when appended to document' );
        assert_equals( befitem, parEl.lastChild, 'properties.item(0) (before test)' );
        assert_equals( befprop, parEl.lastChild, 'properties[0] (before test)' );
        assert_equals( length1, 2, 'length after id is created when appended to document' );
        assert_equals( item0, parEl.firstChild, 'properties.item(0) after id is created when appended to document' );
        assert_equals( prop0, parEl.firstChild, 'properties[0] after id is created when appended to document' );
        assert_equals( item1, parEl.firstChild.firstChild, 'properties.item(1) after id is created when appended to document' );
        assert_equals( prop1, parEl.firstChild.firstChild, 'properties[1] after id is created when appended to document' );
        assert_equals( length2, 1, 'length after removing an attribute when appended to document' );
        assert_equals( afitem, parEl.lastChild, 'properties.item(0) after removing an attribute when appended to document' );
        assert_equals( afprop, parEl.lastChild, 'properties[0] after removing an attribute when appended to document' );
}, 'properties.item and length must update when changing duplicated id of referenced sibling');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 0, 'length (before test)' );
        testEl.itemRef.toggle('id1');
        assert_equals( testEl.properties.length, 1, 'length after itemref is changed' );
        assert_equals( testEl.properties.item(0), parEl.firstChild, 'properties.item(0) after itemref is changed' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] after itemref is changed' );
        testEl.removeAttribute('itemref');
        assert_equals( testEl.properties.length, 0, 'length after itemref is removed' );
        assert_true( !testEl.properties.item(0), 'properties.item(0) itemref is removed' );
        assert_true( !testEl.properties[0], 'properties[0] itemref is removed' );
        document.body.appendChild(parEl);
        var beflength = testEl.properties.length;
        testEl.itemRef.toggle('id1');
        var length1 = testEl.properties.length,
            item0 = testEl.properties.item(0),
            prop0 = testEl.properties[0];
        testEl.removeAttribute('itemref');
        var length2 = testEl.properties.length,
            bitem = !testEl.properties.item(0),
            bprop = !testEl.properties[0];
        document.body.removeChild(parEl);
        assert_equals( beflength, 0, 'length (before test) when appended to document' );
        assert_equals( length1, 1, 'length after itemref is changed when appended to document' );
        assert_equals( item0, parEl.firstChild, 'properties.item(0) after itemref is changed when appended to document' );
        assert_equals( prop0, parEl.firstChild, 'properties[0] after itemref is changed when appended to document' );
        assert_equals( length2, 0, 'length after itemref is removed when appended to document' );
        assert_true( bitem, 'properties.item(0) after itemref is removed when appended to document' );
        assert_true( bprop, 'properties[0] after itemref is removed when appended to document' );
}, 'properties.item and length must update when changing itemref to point to an element');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"><div></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 0, 'length (before test)' );
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( testEl.properties.length, 1, 'length after a referenced element is added' );
        assert_equals( testEl.properties.item(0), parEl.firstChild.lastChild, 'properties.item(0) after a referenced element is added' );
        assert_equals( testEl.properties[0], parEl.firstChild.lastChild, 'properties[0] after a referenced element is added' );
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        assert_equals( testEl.properties.length, 2, 'length after a referenced itemprop is changed' );
        assert_equals( testEl.properties.item(0), parEl.firstChild.firstChild, 'properties.item(0) after a referenced itemprop is changed' );
        assert_equals( testEl.properties[0], parEl.firstChild.firstChild, 'properties[0] after a referenced itemprop is changed' );
        assert_equals( testEl.properties.item(1), parEl.firstChild.lastChild, 'properties.item(1) after a referenced itemprop is changed' );
        assert_equals( testEl.properties[1], parEl.firstChild.lastChild, 'properties[1] after a referenced itemprop is changed' );
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        assert_equals( testEl.properties.length, 1, 'length after a referenced element is removed' );
        assert_equals( testEl.properties.item(0), parEl.firstChild.firstChild, 'properties.item(0) after a referenced element is removed' );
        assert_equals( testEl.properties[0], parEl.firstChild.firstChild, 'properties[0] after a referenced element is removed' );
        assert_true( !testEl.properties.item(1), 'properties.item(1) after a referenced element is removed' );
        assert_true( !testEl.properties[1], 'properties[1] after a referenced element is removed' );
        parEl.innerHTML = '<div id="id1"><div></div></div><div itemscope itemref="id1"></div>';
        testEl = parEl.childNodes[1];
        document.body.appendChild(parEl);
        var beflength = testEl.properties.length;
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        var length1 = testEl.properties.length,
            item0a = testEl.properties.item(0),
            prop0a = testEl.properties[0],
            targ0a = parEl.firstChild.lastChild;
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        var length2 = testEl.properties.length,
            item0b = testEl.properties.item(0),
            prop0b = testEl.properties[0];
            item1b = testEl.properties.item(1),
            prop1b = testEl.properties[1],
            targ0b = parEl.firstChild.firstChild,
            targ1b = parEl.firstChild.lastChild;
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        var length3 = testEl.properties.length,
            item0c = testEl.properties.item(0),
            prop0c = testEl.properties[0];
            item1c = testEl.properties.item(1),
            prop1c = testEl.properties[1],
            targ0c = parEl.firstChild.firstChild;
        document.body.removeChild(parEl);
        assert_equals( beflength, 0, 'length (before test) when appended to document' );
        assert_equals( length1, 1, 'length after a referenced element is added when appended to document' );
        assert_equals( item0a, targ0a, 'properties.item(0) after a referenced element is added when appended to document' );
        assert_equals( prop0a, targ0a, 'properties[0] after a referenced element is added when appended to document' );
        assert_equals( length2, 2, 'length after a referenced itemprop is changed when appended to document' );
        assert_equals( item0b, targ0b, 'properties.item(0) after a referenced itemprop is changed when appended to document' );
        assert_equals( prop0b, targ0b, 'properties[0] after a referenced itemprop is changed when appended to document' );
        assert_equals( item1b, targ1b, 'properties.item(1) after a referenced itemprop is changed when appended to document' );
        assert_equals( prop1b, targ1b, 'properties[1] after a referenced itemprop is changed when appended to document' );
        assert_equals( length3, 1, 'length after a referenced element is removed when appended to document' );
        assert_equals( item0c, targ0c, 'properties.item(0) after a referenced element is removed when appended to document' );
        assert_equals( prop0c, targ0c, 'properties[0] after a referenced element is removed when appended to document' );
        assert_true( !item1c, 'properties.item(1) after a referenced element is removed when appended to document' );
        assert_true( !prop1c, 'properties[1] after a referenced element is removed when appended to document' );
}, 'properties.item and length must update when changing children of elements referenced through itemref');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope itemref="id1"><div itemprop="foo"></div></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 2, 'length (before test)' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] (before test)' );
        assert_equals( testEl.properties[1], testEl.firstChild, 'properties[1] (before test)' );
        document.body.appendChild(testEl);
        var step1length = testEl.properties.length;
        var step1prop0 = testEl.properties[0];
        var step1prop1 = testEl.properties[1];
        parEl.appendChild(testEl);
        assert_equals( step1length, 1, 'length after changing parent' );
        assert_equals( step1prop0, testEl.firstChild, 'properties[0] after changing parent' );
        assert_true( !step1prop1, 'properties[1] after changing parent' );
        assert_equals( testEl.properties.length, 2, 'length after re-parenting' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] after re-parenting' );
        assert_equals( testEl.properties[1], testEl.firstChild, 'properties[1] after re-parenting' );
        document.body.appendChild(parEl);
        var step2length = testEl.properties.length;
        var step2prop0 = testEl.properties[0];
        var step2prop1 = testEl.properties[1];
        document.createElement('div').appendChild(testEl);
        var step3length = testEl.properties.length;
        var step3prop0 = testEl.properties[0];
        var step3prop1 = testEl.properties[1];
        parEl.appendChild(testEl);
        var step4length = testEl.properties.length;
        var step4prop0 = testEl.properties[0];
        var step4prop1 = testEl.properties[1];
        document.body.removeChild(parEl);
        assert_equals( step2length, 2, 'length (before test) when appended to document' );
        assert_equals( step2prop0, parEl.firstChild, 'properties[0] (before test) when appended to document' );
        assert_equals( step2prop1, testEl.firstChild, 'properties[1] (before test) when appended to document' );
        assert_equals( step3length, 1, 'length after changing parent when appended to document' );
        assert_equals( step3prop0, testEl.firstChild, 'properties[0] after changing parent when appended to document' );
        assert_true( !step3prop1, 'properties[1] after changing parent when appended to document' );
        assert_equals( step4length, 2, 'length after re-parenting when appended to document' );
        assert_equals( step4prop0, parEl.firstChild, 'properties[0] after re-parenting when appended to document' );
        assert_equals( step4prop1, testEl.firstChild, 'properties[1] after re-parenting when appended to document' );
}, 'properties.item and length must update when appending elements with itemref to different parents');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div><div itemprop="foo"></div></div>');
        assert_equals( testEl.properties.length, 1, 'length (before test)' );
        assert_equals( testEl.properties.item(0), testEl.firstChild.firstChild, 'properties.item(0) (before test)' );
        assert_equals( testEl.properties[0], testEl.firstChild.firstChild, 'properties[0] (before test)' );
        testEl.firstChild.itemScope = true;
        assert_equals( testEl.properties.length, 0, 'length after setting itemscope' );
        assert_true( !testEl.properties.item(0), 'properties.item(0) after setting itemscope' );
        assert_true( !testEl.properties[0], 'properties[0] after setting itemscope' );
        testEl.firstChild.removeAttribute('itemscope');
        assert_equals( testEl.properties.length, 1, 'length after removing itemscope attribute' );
        assert_equals( testEl.properties.item(0), testEl.firstChild.firstChild, 'properties.item(0) after removing itemscope attribute' );
        assert_equals( testEl.properties[0], testEl.firstChild.firstChild, 'properties[0] after removing itemscope attribute' );
}, 'properties.item and length must update when changing itemscope of children');
//properties.namedItem
test(function () {
        assert_equals( typeof makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>').properties.namedItem('foo'), 'object' );
}, 'the namedItem must return an object');
test(function () {
        assert_equals( typeof makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>').properties['foo'], 'object' );
}, '.properties[] must also act as .properties.namedItem() when there are matching properties');
test(function () {
        assert_equals( typeof makeEl('div',{itemscope:'itemscope'},'').properties.namedItem('foo'), 'object' );
}, 'the namedItem must return an object even if there are no matching properties');
test(function () {
        assert_equals( typeof makeEl('div',{itemscope:'itemscope'},'').properties['foo'], 'undefined' );
}, '.properties[] must return undefined when no property exists with the given name');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        var PNL = testEl.properties.namedItem('foo');
        assert_equals( PNL, testEl.properties.namedItem('foo'), 'before modification' );
        testEl.innerHTML = '';
        assert_equals( PNL, testEl.properties.namedItem('foo'), 'after modification' );
}, 'namedItem must return the same object for the same property name');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_false( testEl.properties.namedItem('foo') == testEl.properties.namedItem('bar') );
}, 'namedItem must return a different object for a different property name');
test(function () {
        assert_false( makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>').properties.namedItem('foo') == makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>').properties.namedItem('foo') );
}, 'namedItem must return a different object for different elements with the same property name');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_equals( testEl.properties.namedItem('foo'), testEl.properties['foo'] );
}, 'namedItem() and properties[] must return the same object for the same property name');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_true( testEl.properties.namedItem('foo') instanceof PropertyNodeList, 'instanceof PropertyNodeList' );
        assert_true( testEl.properties.namedItem('foo') instanceof NodeList, 'instanceof NodeList' );
        PropertyNodeList.prototype.customProperty = true;
        NodeList.prototype.anotherCustomProperty = true;
        assert_true( testEl.properties.namedItem('foo').customProperty, 'inheritance from PropertyNodeList' );
        assert_true( testEl.properties.namedItem('foo').anotherCustomProperty, 'inheritance from NodeList' );
        PropertyNodeList.prototype.anotherCustomProperty = false;
        assert_false( testEl.properties.anotherCustomProperty, 'shadowing by PropertyNodeList' );
}, 'the properties property must implement PropertyNodeList and NodeList');
test(function () {
        var failed = false, elem = makeEl('div',{itemscope:'itemscope'});
        try {
                elem.properties.namedItem = 'pass';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.properties.namedItem,'pass');
        assert_false(failed,'an error was thrown');
}, 'the namedItem property must be read/write');
test(function () {
        //also tests for sort ordering, which is fairly simple in this case
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="baz qux"></div></div><div itemprop="foo"></div>');
        assert_equals( testEl.properties.namedItem('foo').length, 2, 'length of foo' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'length of bar' );
        assert_equals( testEl.properties.namedItem('baz').length, 1, 'length of baz' );
        assert_equals( testEl.properties.namedItem('qux').length, 1, 'length of qux' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'first foo' );
        assert_equals( testEl.properties.namedItem('foo')[1], testEl.lastChild, 'last foo' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.childNodes[1], 'bar' );
        assert_equals( testEl.properties.namedItem('baz')[0], testEl.childNodes[1].firstChild, 'baz' );
        assert_equals( testEl.properties.namedItem('qux')[0], testEl.childNodes[1].firstChild, 'qux' );
}, 'PropertyNodeList must contain the correct properties');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="FOO"></div><div itemprop="foo FOO foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.namedItem('foo').length, 2, 'length of foo' );
        assert_equals( testEl.properties.namedItem('FOO').length, 2, 'length of FOO' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'first foo' );
        assert_equals( testEl.properties.namedItem('foo')[1], testEl.childNodes[1].lastChild, 'last foo' );
        assert_equals( testEl.properties.namedItem('FOO')[0], testEl.childNodes[1].firstChild, 'first FOO' );
        assert_equals( testEl.properties.namedItem('FOO')[1], testEl.childNodes[1].lastChild, 'last FOO' );
}, 'PropertyNodeList must be case sensitive');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo bar"></div>');
        assert_equals( testEl.properties.namedItem('foo bar').length, 0, 'space' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo\tbar"></div>');
        assert_equals( testEl.properties.namedItem('foo\tbar').length, 0, 'tab' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo\rbar"></div>');
        assert_equals( testEl.properties.namedItem('foo\rbar').length, 0, 'carriage return' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo\nbar"></div>');
        assert_equals( testEl.properties.namedItem('foo\nbar').length, 0, 'newline' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo\fbar"></div>');
        assert_equals( testEl.properties.namedItem('foo\fbar').length, 0, 'formfeed' );
}, 'namedItem must not match property names containing whitespace');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="|§!&quot;#¤%&/()=?`\\@£${[]}´€^¨~\'*,;.:-_&lt;&gt;åøæÅØÆ"></div>');
        assert_equals( testEl.properties.namedItem('|§!"#¤%&/()=?`\\@£${[]}´€^¨~\'*,;.:-_<>åøæÅØÆ').length, 1 );
}, 'namedItem must match property names containing other special characters');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'});
        var PNL = testEl.properties.namedItem('foo');
        testEl.innerHTML = '<div itemprop="foo"></div>';
        assert_equals( PNL.length, 1 );
        assert_equals( PNL[0], testEl.firstChild );
}, 'PropertyNodeList must be live');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemscope><div itemprop="foo"></div><div itemprop="bar"></div></div>');
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'length of foo' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'item 0' );
        assert_equals( testEl.properties.namedItem('bar').length, 0, 'length of bar' );
}, 'PropertyNodeList must ignore properties of nested items');
test(function () {

        //note, itemref ordering is reversed compared with the next test to catch failed sorting algorithms - not that that should make much difference here
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id2 id1"><div itemprop="foo bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        document.body.appendChild(parEl);
        var fooLength = testEl.properties.namedItem('foo').length;
        var barLength = testEl.properties.namedItem('bar').length;
        var bazLength = testEl.properties.namedItem('baz').length;
        var quxLength = testEl.properties.namedItem('qux').length;
        var foo0 = testEl.properties.namedItem('foo')[0];
        var foo1 = testEl.properties.namedItem('foo')[1];
        var bar0 = testEl.properties.namedItem('bar')[0];
        var baz0 = testEl.properties.namedItem('baz')[0];
        var qux0 = testEl.properties.namedItem('qux')[0];
        document.body.removeChild(parEl);
        assert_equals( fooLength, 2, 'foo length' );
        assert_equals( barLength, 1, 'bar length' );
        assert_equals( bazLength, 1, 'baz length' );
        assert_equals( quxLength, 1, 'qux length' );
        assert_equals( foo0, parEl.firstChild, 'foo 0' );
        assert_equals( foo1, testEl.firstChild, 'foo 1' );
        assert_equals( bar0, testEl.firstChild, 'bar 0' );
        assert_equals( baz0, parEl.lastChild, 'baz 0' );
        assert_equals( qux0, parEl.lastChild.firstChild, 'qux 0' );
}, 'PropertyNodeList must see items added with itemref when attached to the document\'s DOM');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id1 id2"><div itemprop="foo bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.namedItem('foo').length, 2, 'foo length' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar length' );
        assert_equals( testEl.properties.namedItem('baz').length, 1, 'baz length' );
        assert_equals( testEl.properties.namedItem('qux').length, 1, 'qux length' );
        assert_equals( testEl.properties.namedItem('foo')[0], parEl.firstChild, 'foo 0' );
        assert_equals( testEl.properties.namedItem('foo')[1], testEl.firstChild, 'foo 1' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild, 'bar 0' );
        assert_equals( testEl.properties.namedItem('baz')[0], parEl.lastChild, 'baz 0' );
        assert_equals( testEl.properties.namedItem('qux')[0], parEl.lastChild.firstChild, 'qux 0' );
}, 'PropertyNodeList must see items added with itemref');
test(function () {
        //this one also tests the live object just in case - further ones will not always do this as its live status will already have been well established
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        var PNL = testEl.properties.namedItem('foo');
        testEl.removeAttribute('itemscope');
        assert_equals( testEl.properties.namedItem('foo').length, 0, 'removing attribute' );
        assert_equals( PNL.length, 0, 'removing attribute (live)' );
        assert_true( !testEl.properties['foo'], 'removing attribute []' );
        testEl.itemScope = true;
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'setting itemScope' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'property 0 after setting itemScope' );
        assert_equals( PNL.length, 1, 'setting itemScope (live)' );
        assert_equals( PNL[0], testEl.firstChild, 'property 0 after setting itemScope (live)' );
        assert_false( !testEl.properties['foo'], 'setting itemScope []' );
}, 'PropertyNodeList must update when adding itemscope on the root');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'foo length (before test)' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'foo 0 (before test)' );
        assert_equals( testEl.properties.namedItem('bar').length, 0, 'bar length (before test)' );
        testEl.appendChild(makeEl('div',{itemprop:'bar'}));
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'foo length after adding a child' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'foo 0 after adding a child' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar length after adding a child' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'bar 0 after adding a child' );
        testEl.lastChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( testEl.properties.namedItem('foo').length, 2, 'foo length after adding a child with duplicated name' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'foo 0 after adding a child with duplicated name' );
        assert_equals( testEl.properties.namedItem('foo')[1], testEl.lastChild.firstChild, 'foo 1 after adding a child with duplicated name' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar length after adding a child with duplicated name' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'bar 0 after adding a child with duplicated name' );
        testEl.lastChild.removeChild(testEl.lastChild.firstChild);
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'foo length after removing a child' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar length after removing a child' );
}, 'PropertyNodeList must update when adding property elements');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="foo"></div>');
        var PNL = testEl.properties.namedItem('foo');
        assert_equals( PNL[0], testEl.firstChild, 'item 0 (before test)' );
        assert_equals( PNL[1], testEl.lastChild, 'item 1 (before test)' );
        testEl.appendChild(testEl.firstChild);
        assert_equals( PNL[0], testEl.firstChild, 'item 0 (after test)' );
        assert_equals( PNL[1], testEl.lastChild, 'item 1 (after test)' );
}, 'PropertyNodeList must update when re-ordering property elements');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div></div>');
        var PNLfoo = testEl.properties.namedItem('foo'), PNLbar = testEl.properties.namedItem('bar');
        assert_equals( PNLfoo.length, 1, 'foo length (before test)' );
        assert_equals( PNLbar.length, 0, 'bar length (before test)' );
        assert_equals( PNLfoo[0], testEl.firstChild, 'foo[0] (before test)' );
        testEl.lastChild.itemProp.toggle('bar');
        assert_equals( PNLfoo.length, 1, 'foo length after adding a token' );
        assert_equals( PNLbar.length, 1, 'bar length after adding a token' );
        assert_equals( PNLfoo[0], testEl.firstChild, 'foo[0] after adding a token' );
        assert_equals( PNLbar[0], testEl.lastChild, 'bar[0] after adding a token' );
        testEl.lastChild.itemProp.add('foo');
        assert_equals( PNLfoo.length, 2, 'foo length after adding a duplicated token' );
        assert_equals( PNLbar.length, 1, 'bar length after adding a duplicated token' );
        assert_equals( PNLfoo[0], testEl.firstChild, 'foo[0] after adding a duplicated token' );
        assert_equals( PNLfoo[1], testEl.lastChild, 'foo[1] after adding a duplicated token' );
        assert_equals( PNLbar[0], testEl.lastChild, 'bar[0] after adding a duplicated token' );
        testEl.lastChild.removeAttribute('itemprop');
        assert_equals( PNLfoo.length, 1, 'foo length after removing an attribute' );
        assert_equals( PNLbar.length, 0, 'bar length after removing an attribute' );
        assert_equals( PNLfoo[0], testEl.firstChild, 'foo[0] after removing an attribute' );
}, 'PropertyNodeList must update when changing itemProp of children');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        var PNLfoo = testEl.properties.namedItem('foo'), PNLbar = testEl.properties.namedItem('bar');
        assert_equals( PNLfoo.length, 0, 'foo length (before test)' );
        assert_equals( PNLbar.length, 0, 'bar length (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( PNLfoo.length, 1, 'foo length after id is created' );
        assert_equals( PNLbar.length, 1, 'bar length after id is created' );
        assert_equals( PNLfoo[0], parEl.firstChild, 'foo[0] after id is created' );
        assert_equals( PNLbar[0], parEl.firstChild.firstChild, 'bar[0] after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( PNLfoo.length, 0, 'foo length after removing an attribute' );
        assert_equals( PNLbar.length, 0, 'bar length after removing an attribute' );
        document.body.appendChild(parEl);
        var fooLength0 = PNLfoo.length;
        var barLength0 = PNLbar.length;
        parEl.firstChild.id = 'id1';
        var fooLength1 = PNLfoo.length;
        var barLength1 = PNLbar.length;
        var foo0 = PNLfoo[0];
        var bar0 = PNLbar[0];
        parEl.firstChild.removeAttribute('id');
        var fooLength2 = PNLfoo.length;
        var barLength2 = PNLbar.length;
        document.body.removeChild(parEl);
        assert_equals( fooLength0, 0, 'foo length (before test) when appended to document' );
        assert_equals( barLength0, 0, 'bar length (before test) when appended to document' );
        assert_equals( fooLength1, 1, 'foo length after id is created when appended to document' );
        assert_equals( barLength1, 1, 'bar length after id is created when appended to document' );
        assert_equals( foo0, parEl.firstChild, 'foo[0] after id is created when appended to document' );
        assert_equals( bar0, parEl.firstChild.firstChild, 'bar[0] after id is created when appended to document' );
        assert_equals( fooLength2, 0, 'foo length after removing an attribute when appended to document' );
        assert_equals( barLength2, 0, 'bar length after removing an attribute when appended to document' );
}, 'PropertyNodeList must update when changing id of referenced sibling');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[1];
        var PNLfoo = testEl.properties.namedItem('foo'), PNLbar = testEl.properties.namedItem('bar'), PNLbaz = testEl.properties.namedItem('baz');
        assert_equals( PNLfoo.length, 0, 'foo length (before test)' );
        assert_equals( PNLbar.length, 0, 'bar length (before test)' );
        assert_equals( PNLbaz.length, 1, 'baz length (before test)' );
        assert_equals( PNLbaz[0], parEl.lastChild, 'baz[0] (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( PNLfoo.length, 1, 'foo length after id is created' );
        assert_equals( PNLbar.length, 1, 'bar length after id is created' );
        assert_equals( PNLbaz.length, 0, 'baz length after id is created' );
        assert_equals( PNLfoo[0], parEl.firstChild, 'foo[0] after id is created' );
        assert_equals( PNLbar[0], parEl.firstChild.firstChild, 'bar[0] after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( PNLfoo.length, 0, 'foo length after removing an attribute' );
        assert_equals( PNLbar.length, 0, 'bar length after removing an attribute' );
        assert_equals( PNLbaz.length, 1, 'baz length after removing an attribute' );
        assert_equals( PNLbaz[0], parEl.lastChild, 'baz[0] after removing an attribute' );
        document.body.appendChild(parEl);
        var fooLength0 = PNLfoo.length;
        var barLength0 = PNLbar.length;
        var bazLength0 = PNLbaz.length;
        var baz0 = PNLbaz[0];
        parEl.firstChild.id = 'id1';
        var fooLength1 = PNLfoo.length;
        var barLength1 = PNLbar.length;
        var bazLength1 = PNLbaz.length;
        var foo0 = PNLfoo[0];
        var bar0 = PNLbar[0];
        parEl.firstChild.removeAttribute('id');
        var fooLength2 = PNLfoo.length;
        var barLength2 = PNLbar.length;
        var bazLength2 = PNLbaz.length;
        var baz1 = PNLbaz[0];
        document.body.removeChild(parEl);
        assert_equals( fooLength0, 0, 'foo length (before test) when appended to document' );
        assert_equals( barLength0, 0, 'bar length (before test) when appended to document' );
        assert_equals( bazLength0, 1, 'baz length (before test)' );
        assert_equals( baz0, parEl.lastChild, 'baz[0] (before test)' );
        assert_equals( fooLength1, 1, 'foo length after id is created when appended to document' );
        assert_equals( barLength1, 1, 'bar length after id is created when appended to document' );
        assert_equals( bazLength1, 0, 'baz length after id is created' );
        assert_equals( foo0, parEl.firstChild, 'foo[0] after id is created when appended to document' );
        assert_equals( bar0, parEl.firstChild.firstChild, 'bar[0] after id is created when appended to document' );
        assert_equals( fooLength2, 0, 'foo length after removing an attribute when appended to document' );
        assert_equals( barLength2, 0, 'bar length after removing an attribute when appended to document' );
        assert_equals( bazLength2, 1, 'baz length after removing an attribute' );
        assert_equals( baz0, parEl.lastChild, 'baz[0] after removing an attribute' );
}, 'PropertyNodeList must update when changing duplicated id of referenced sibling');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope></div>');
        var testEl = parEl.childNodes[1];
        var PNL = testEl.properties.namedItem('foo');
        assert_equals( PNL.length, 0, 'length (before test)' );
        testEl.itemRef.toggle('id1');
        assert_equals( PNL.length, 1, 'length after itemref is changed' );
        assert_equals( PNL[0], parEl.firstChild, 'item 0 after itemref is changed' );
        testEl.removeAttribute('itemref');
        assert_equals( PNL.length, 0, 'length after itemref is removed' );
        assert_true( !PNL[0], 'item 0 after itemref is removed' );
        document.body.appendChild(parEl);
        var length0 = PNL.length;
        testEl.itemRef.toggle('id1');
        var length1 = PNL.length;
        var foo0 = PNL[0];
        testEl.removeAttribute('itemref');
        var length2 = PNL.length;
        var foo1 = PNL[0];
        document.body.removeChild(parEl);
        assert_equals( length0, 0, 'length (before test) when appended to document' );
        assert_equals( length1, 1, 'length after itemref is changed when appended to document' );
        assert_equals( foo0, parEl.firstChild, 'item 0 after itemref is changed when appended to document' );
        assert_equals( length2, 0, 'length after itemref is removed when appended to document' );
        assert_true( !foo1, 'item 0 after itemref is removed when appended to document' );
}, 'PropertyNodeList must update when changing itemref to point to an element');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"><div></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        var PNLfoo = testEl.properties.namedItem('foo'), PNLbar = testEl.properties.namedItem('bar');
        assert_equals( PNLfoo.length, 0, 'foo length (before test)' );
        assert_equals( PNLbar.length, 0, 'bar length (before test)' );
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( PNLfoo.length, 1, 'foo length after a referenced element is added' );
        assert_equals( PNLbar.length, 0, 'bar length after a referenced element is added' );
        assert_equals( PNLfoo.item(0), parEl.firstChild.lastChild, 'foo 0 after a referenced element is added' ); //uses item just for the fun of it
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        assert_equals( PNLfoo.length, 1, 'foo length after a referenced itemprop is changed' );
        assert_equals( PNLbar.length, 1, 'bar length after a referenced itemprop is changed' );
        assert_equals( PNLfoo[0], parEl.firstChild.lastChild, 'foo 0 after a referenced element is added' );
        assert_equals( PNLbar[0], parEl.firstChild.firstChild, 'bar 0 after a referenced element is added' );
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        assert_equals( PNLfoo.length, 1, 'foo length after a referenced element is removed' );
        assert_equals( PNLbar.length, 0, 'bar length after a referenced element is removed' );
        assert_equals( PNLfoo[0], parEl.firstChild.firstChild, 'foo 0 after a referenced element is removed' );
        assert_true( !PNLbar[0], 'bar 0 after a referenced element is removed' );
        parEl.innerHTML = '<div id="id1"><div></div></div><div itemscope itemref="id1"></div>';
        testEl = parEl.childNodes[1];
        PNLfoo = testEl.properties.namedItem('foo');
        PNLbar = testEl.properties.namedItem('bar');
        document.body.appendChild(parEl);
        var step1fooLength = PNLfoo.length;
        var step1barLength = PNLbar.length;
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        var step2fooLength = PNLfoo.length;
        var step2barLength = PNLbar.length;
        var step2foo0 = PNLfoo.item(0); //uses item just for the fun of it
        var step2fooExpected = parEl.firstChild.lastChild;
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        var step3fooLength = PNLfoo.length;
        var step3barLength = PNLbar.length;
        var step3foo0 = PNLfoo[0];
        var step3bar0 = PNLbar[0];
        var step3fooExpected = parEl.firstChild.lastChild;
        var step3barExpected = parEl.firstChild.firstChild;
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        var step4fooLength = PNLfoo.length;
        var step4barLength = PNLbar.length;
        var step4foo0 = PNLfoo[0];
        var step4bar0 = PNLbar[0];
        var step4fooExpected = parEl.firstChild.firstChild;
        document.body.removeChild(parEl);
        assert_equals( step1fooLength, 0, 'foo length (before test) when appended to document' );
        assert_equals( step1barLength, 0, 'bar length (before test) when appended to document' );
        assert_equals( step2fooLength, 1, 'foo length after a referenced element is added when appended to document' );
        assert_equals( step2barLength, 0, 'bar length after a referenced element is added when appended to document' );
        assert_equals( step2foo0, step2fooExpected, 'foo 0 after a referenced element is added when appended to document' );
        assert_equals( step3fooLength, 1, 'foo length after a referenced itemprop is changed when appended to document' );
        assert_equals( step3barLength, 1, 'bar length after a referenced itemprop is changed when appended to document' );
        assert_equals( step3foo0, step3fooExpected, 'foo 0 after a referenced element is added when appended to document' );
        assert_equals( step3bar0, step3barExpected, 'bar 0 after a referenced element is added when appended to document' );
        assert_equals( step4fooLength, 1, 'foo length after a referenced element is removed when appended to document' );
        assert_equals( step4barLength, 0, 'bar length after a referenced element is removed when appended to document' );
        assert_equals( step4foo0, step4fooExpected, 'foo 0 after a referenced element is removed when appended to document' );
        assert_true( !step4bar0, 'bar 0 after a referenced element is removed when appended to document' );
}, 'PropertyNodeList must update when changing children of elements referenced through itemref');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope itemref="id1"><div itemprop="foo"></div></div>');
        var testEl = parEl.childNodes[1];
        var PNL = testEl.properties.namedItem('foo');
        assert_equals( PNL.length, 2, 'length (before test)' );
        assert_equals( PNL[0], parEl.firstChild, 'item 0 (before test)' );
        assert_equals( PNL[1], testEl.firstChild, 'item 1 (before test)' );
        document.body.appendChild(testEl);
        var step1length = PNL.length;
        var step1prop0 = PNL[0];
        var step1prop1 = PNL[1];
        parEl.appendChild(testEl);
        assert_equals( step1length, 1, 'length after changing parent' );
        assert_equals( step1prop0, testEl.firstChild, 'item 0 after changing parent' );
        assert_true( !step1prop1, 'item 1 after changing parent' );
        assert_equals( PNL.length, 2, 'length after re-parenting' );
        assert_equals( PNL[0], parEl.firstChild, 'item 0 after re-parenting' );
        assert_equals( PNL[1], testEl.firstChild, 'item 1 after re-parenting' );
        document.body.appendChild(parEl);
        var step2length = PNL.length;
        var step2prop0 = PNL[0];
        var step2prop1 = PNL[1];
        document.createElement('div').appendChild(testEl);
        var step3length = PNL.length;
        var step3prop0 = PNL[0];
        var step3prop1 = PNL[1];
        parEl.appendChild(testEl);
        var step4length = PNL.length;
        var step4prop0 = PNL[0];
        var step4prop1 = PNL[1];
        document.body.removeChild(parEl);
        assert_equals( step2length, 2, 'length (before test) when appended to document' );
        assert_equals( step2prop0, parEl.firstChild, 'item 0 (before test) when appended to document' );
        assert_equals( step2prop1, testEl.firstChild, 'item 1 (before test) when appended to document' );
        assert_equals( step3length, 1, 'length after changing parent when appended to document' );
        assert_equals( step3prop0, testEl.firstChild, 'item 0 after changing parent when appended to document' );
        assert_true( !step3prop1, 'item 1 after changing parent when appended to document' );
        assert_equals( step4length, 2, 'length after re-parenting when appended to document' );
        assert_equals( step4prop0, parEl.firstChild, 'item 0 after re-parenting when appended to document' );
        assert_equals( step4prop1, testEl.firstChild, 'item 1 after re-parenting when appended to document' );
}, 'PropertyNodeList must update when appending elements with itemref to different parents');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div><div itemprop="foo"></div></div>');
        var PNL = testEl.properties.namedItem('foo');
        assert_equals( PNL.length, 1, 'length (before test)' );
        assert_equals( PNL[0], testEl.firstChild.firstChild, 'foo 0 (before test)' );
        testEl.firstChild.itemScope = true;
        assert_equals( PNL.length, 0, 'length after setting itemscope' );
        assert_true( !PNL[0], 'foo 0 after setting itemscope' );
        testEl.firstChild.removeAttribute('itemscope');
        assert_equals( PNL.length, 1, 'length after removing itemscope attribute' );
        assert_equals( PNL[0], testEl.firstChild.firstChild, 'foo 0 after removing itemscope attribute' );
}, 'PropertyNodeList must update when changing itemscope of children');
//PropertyNodeList.getValues
test(function () {
        var valuesArray = makeEl('div',{}).properties.namedItem('foo').getValues();
        assert_true( valuesArray instanceof Array, 'instanceof test' );
        Array.prototype.customProp = true;
        assert_true( valuesArray.customProp, 'inheritance test' );
}, 'getValues must return an array');
test(function () {
        var testEl = makeEl('div',{});
        var props = testEl.properties.namedItem('foo');
        assert_not_equals( props.getValues(), props.getValues() );
}, 'getValues must always return a newly constructed array');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"></div>');
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1 id2'});
        parEl.appendChild(testEl);
        testEl.appendChild(makeEl('meta',{itemprop:'foo',content:'test'}));
        testEl.appendChild(makeEl('audio',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('embed',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('iframe',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('img',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('source',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('track',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('video',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('a',{itemprop:'foo',href:urls.base},'contained text'));
        testEl.appendChild(makeEl('area',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('link',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('object',{itemprop:'foo',data:urls.base},'contained text'));
        parEl.appendChild(makeEl('time',{itemprop:'foo',id:'id2'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('time',{itemprop:'foo',datetime:'test'},'te <span itemprop="foo" itemscope>st</span> ing'));
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('madeuponthespot',{itemprop:'foo'},'te <span itemprop="foo" itemscope>st</span> ing'));
        var PNL = testEl.properties.namedItem('foo');
        var valuesArray = PNL.getValues();
        for( var i = 0; i < PNL.length; i++ ) {
                assert_equals( valuesArray[i], PNL[i].itemValue, 'property index ' + i + ', tag ' + PNL[i].tagName );
        }
        assert_equals( valuesArray.length, 20, 'length' );
}, 'getValues array must contain the same item values as itemValue would return for the given properties');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"></div>');
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1 id2'});
        parEl.appendChild(testEl);
        testEl.appendChild(makeEl('meta',{itemprop:'foo',content:'test'}));
        testEl.appendChild(makeEl('audio',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('embed',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('iframe',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('img',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('source',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('track',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('video',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('a',{itemprop:'foo',href:urls.base},'contained text'));
        testEl.appendChild(makeEl('area',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('link',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('object',{itemprop:'foo',data:urls.base},'contained text'));
        parEl.appendChild(makeEl('time',{itemprop:'foo',id:'id2'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('time',{itemprop:'foo',datetime:'test'},'te <span itemprop="foo" itemscope>st</span> ing'));
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('madeuponthespot',{itemprop:'foo'},'te <span itemprop="foo" itemscope>st</span> ing'));
        var PNL = testEl.properties.namedItem('foo');
        var valuesArray = PNL.getValues();
        var staticArray = [];
        for( var i = 0; i < PNL.length; i++ ) {
                staticArray[i] = PNL[i].itemValue;
        }
        testEl.innerHTML = '';
        parEl.firstChild.firstChild.childNodes[1].itemScope = false;
        assert_equals( valuesArray.length, staticArray.length, 'length after modification' );
        for( var j = 0; j < staticArray.length; j++ ) {
                assert_equals( valuesArray[j], staticArray[j], 'property index ' + j );
        }
        assert_equals( valuesArray[1], parEl.firstChild.firstChild.childNodes[1], 'retaining pointer after modification' );
        staticArray = null;
        parEl.firstChild.firstChild.innerHTML = '';
        assert_equals( valuesArray[1] && valuesArray[1].nodeType, 1, 'retaining pointer after removal' );
}, 'getValues array must not be live');
//names
test(function () {
        assert_equals( typeof makeEl('div',{}).properties.names, 'object' );
}, 'the names property must be an object');
test(function () {
        var testEl = makeEl('div',{});
        assert_true( testEl.properties.names instanceof DOMStringList, 'instanceof DOMStringList' );
        DOMStringList.prototype.stringCustomProperty = true;
        assert_true( testEl.properties.names.stringCustomProperty, 'inheritance from DOMStringList' );
}, 'the names property must implement DOMStringList');
test(function () {
        var failed = false, elem = makeEl('div',{itemscope:'itemscope'}), realList = elem.properties.names;
        try {
                elem.properties.names = '';
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.properties.names,realList);
        assert_false(failed,'an error was thrown');
}, 'the names property must be read-only');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.properties.names, testEl.properties.names );
}, 'the names property must always reference the same object');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.properties.names.item(0), null, 'item(0)' );
        assert_equals( testEl.properties.names.item(-1), null, 'item(-1)' );
}, 'names.item() must return null for out of range indexes');
test(function () {
        var testEl = makeEl('div',{});
        assert_equals( testEl.properties.names[0], window.undefined, '[0]' );
        assert_equals( testEl.properties.names[-1], window.undefined, '[-1]' );
}, 'names[index] must return undefined for out of range indexes');
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        assert_equals( testEl.properties.names.length, 0, 'length' );
        assert_true( !testEl.properties.names.item(0), 'item(0)' );
        assert_true( !testEl.properties.names[0], '[0]' );
        assert_false( testEl.properties.names.contains('foo'), 'contains' );
}, 'the names collection must be empty if the element does not have an itemscope property');
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        testEl.itemScope = true;
        assert_equals( testEl.properties.names.length, 1, 'length' );
        assert_equals( testEl.properties.names.item(0), 'foo', 'item(0)' );
        assert_equals( testEl.properties.names[0], 'foo', '[0]' );
        assert_true( testEl.properties.names.contains('foo'), 'contains' );
}, 'the names collection must become populated if the element is given an itemscope property');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo">bar</div>');
        testEl.itemScope = false;
        assert_equals( testEl.properties.names.length, 0, 'length' );
        assert_true( !testEl.properties.names.item(0), 'item(0)' );
        assert_true( !testEl.properties.names[0], '[0]' );
        assert_false( testEl.properties.names.contains('foo'), 'contains' );
}, 'the names collection must become empty if the element\'s itemscope property is removed');
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        testEl.properties.names.item = 'test';
        testEl.properties.names.contains = 'test';
        assert_equals( testEl.properties.names.item, 'test', 'item' );
        assert_equals( testEl.properties.names.contains, 'test', 'contains' );
}, 'the names.item and names.contains methods should be overwriteable');
test(function () {
        var testEl = makeEl('div',{},'<div itemprop="foo">bar</div>');
        testEl.properties.names.localCustomProperty = 'test';
        assert_equals( testEl.properties.names.localCustomProperty, 'test' );
}, 'the names.customProperty should be writeable');
test(function () {
        //WebIDL and ECMAScript 5 - a readonly property has a getter but not a setter
        //ES5 makes [[Put]] fail but not throw
        var failed = false;
        var elem = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo">bar</div>');
        try {
                elem.properties.names.length = 0;
        } catch(e) {
                failed = e;
        }
        assert_equals(elem.properties.names.length,1);
        assert_false(failed,'an error was thrown');
}, 'names.length must be read-only');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.names.length, 4 );
}, 'names.length must be the total number of property names');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div><div itemprop="foo"></div></div><div itemprop="baz \t\r\n\fqux"></div>');
        assert_equals( testEl.properties.names.item(0), 'foo', 'item(0)' );
        assert_equals( testEl.properties.names.item(1), 'bar', 'item(1)' );
        assert_equals( testEl.properties.names.item(2), 'baz', 'item(2)' );
        assert_equals( testEl.properties.names.item(3), 'qux', 'item(3)' );
        assert_equals( testEl.properties.names[0], 'foo', '[0]' );
        assert_equals( testEl.properties.names[1], 'bar', '[1]' );
        assert_equals( testEl.properties.names[2], 'baz', '[2]' );
        assert_equals( testEl.properties.names[3], 'qux', '[3]' );
}, 'names.item must give each property name in tree order');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar BAR bar"><div itemprop="FOO"></div><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_equals( testEl.properties.names.length, 6, 'length' );
        assert_equals( testEl.properties.names.item(0), 'foo', 'item(0)' );
        assert_equals( testEl.properties.names.item(1), 'bar', 'item(1)' );
        assert_equals( testEl.properties.names.item(2), 'BAR', 'item(2)' );
        assert_equals( testEl.properties.names.item(3), 'FOO', 'item(3)' );
        assert_equals( testEl.properties.names.item(4), 'baz', 'item(4)' );
        assert_equals( testEl.properties.names.item(5), 'qux', 'item(5)' );
        assert_equals( testEl.properties.names[0], 'foo', '[0]' );
        assert_equals( testEl.properties.names[1], 'bar', '[1]' );
        assert_equals( testEl.properties.names[2], 'BAR', '[2]' );
        assert_equals( testEl.properties.names[3], 'FOO', '[3]' );
        assert_equals( testEl.properties.names[4], 'baz', '[4]' );
        assert_equals( testEl.properties.names[5], 'qux', '[5]' );
}, 'names must be case sensitive');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="FOO"></div><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
        assert_true( testEl.properties.names.contains('foo'), 'foo' );
        assert_true( testEl.properties.names.contains('FOO'), 'FOO' );
        assert_true( testEl.properties.names.contains('bar'), 'bar' );
        assert_false( testEl.properties.names.contains('BAR'), 'BAR' );
        assert_true( testEl.properties.names.contains('baz'), 'baz' );
        assert_true( testEl.properties.names.contains('qux'), 'qux' );
        assert_false( testEl.properties.names.contains('madeup'), 'madeup' );
}, 'names.contains must return boolean if the name exists');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="1"></div>');
        assert_equals( testEl.properties.names.item('1'), null );
}, 'names.item must cast to number');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="1"></div>');
        assert_true( testEl.properties.names.contains({ valueOf: function () { return 2; }, toString: function () { return 'foo'; } }), 'object' );
        assert_true( testEl.properties.names.contains(1), 'number' );
}, 'names.contains must cast to string');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"></div>');
        var namesList = testEl.properties.names;
        testEl.innerHTML = '<div itemprop="baz"></div>';
        assert_equals( testEl.properties.names.length, 1, 'length' );
        assert_equals( testEl.properties.names[0], 'baz', '[0]' );
}, 'the names collection must be live');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="|§!&quot;#¤%&/()=?`\\@£${[]}´€^¨~\'*,;.:-_&lt;&gt;åøæÅØÆ"></div>');
        assert_equals( testEl.properties.names[0], '|§!"#¤%&/()=?`\\@£${[]}´€^¨~\'*,;.:-_<>åøæÅØÆ' );
}, 'names must reflect property names containing special characters');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemscope><div itemprop="bar"></div></div>');
        assert_equals( testEl.properties.names.length, 1, 'length' );
        assert_equals( testEl.properties.names[0], 'foo', '[0]' );
        assert_true( testEl.properties.names.contains('foo'), 'contains(foo)' );
        assert_false( testEl.properties.names.contains('bar'), 'contains(bar)' );
}, 'names must ignore properties of nested items');
test(function () {
        //note, itemref ordering is reversed compared with the next test to catch failed sorting algorithms
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id2 id1"><div itemprop="bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        document.body.appendChild(parEl);
        var length = testEl.properties.names.length;
        var names0 = testEl.properties.names[0];
        var names1 = testEl.properties.names[1];
        var names2 = testEl.properties.names[2];
        var names3 = testEl.properties.names[3];
        document.body.removeChild(parEl);
        assert_equals( length, 4, 'length' );
        assert_equals( names0, 'foo', 'names[0]' );
        assert_equals( names1, 'bar', 'names[1]' );
        assert_equals( names2, 'baz', 'names[2]' );
        assert_equals( names3, 'qux', 'names[3]' );
}, 'names must see items added with itemref when attached to the document\'s DOM');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id1"></div><div itemscope itemref="id1 id2"><div itemprop="bar"></div></div><div itemprop="baz" id="id2"><div itemprop="qux"></div></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.names.length, 4, 'length' );
        assert_equals( testEl.properties.names[0], 'foo', 'names[0]' );
        assert_equals( testEl.properties.names[1], 'bar', 'names[1]' );
        assert_equals( testEl.properties.names[2], 'baz', 'names[2]' );
        assert_equals( testEl.properties.names[3], 'qux', 'names[3]' );
}, 'names must see items added with itemref');
test(function () {
        //this one also tests the live object just in case - further ones will not always do this as its live status will already have been well established
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        var DSL = testEl.properties.names;
        testEl.removeAttribute('itemscope');
        assert_equals( testEl.properties.names.length, 0, 'removing attribute' );
        assert_equals( DSL.length, 0, 'removing attribute (live)' );
        assert_true( !testEl.properties.names[0], 'removing attribute [0]' );
        assert_true( !DSL[0], 'removing attribute [0] (live)' );
        testEl.itemScope = true;
        assert_equals( testEl.properties.names.length, 1, 'setting itemScope' );
        assert_equals( DSL.length, 1, 'setting itemScope (live)' );
        assert_equals( testEl.properties.names[0], 'foo', 'names[0] after setting itemScope' );
        assert_equals( DSL[0], 'foo', 'names[0] after setting itemScope (live)' );
}, 'names must update when adding itemscope on the root');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div>');
        assert_equals( testEl.properties.names.length, 1, 'length (before test)' );
        assert_equals( testEl.properties.names[0], 'foo', 'item 0 (before test)' );
        testEl.appendChild(makeEl('div',{itemprop:'bar'}));
        assert_equals( testEl.properties.names.length, 2, 'length after adding a child' );
        assert_equals( testEl.properties.names[0], 'foo', 'item 0 after adding a child' );
        assert_equals( testEl.properties.names[1], 'bar', 'item 1 after adding a child' );
        testEl.lastChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( testEl.properties.names.length, 2, 'foo length after adding a child with duplicated name' );
        assert_equals( testEl.properties.names[0], 'foo', 'item 0 after adding a child with duplicated name' );
        assert_equals( testEl.properties.names[1], 'bar', 'item 1 after adding a child with duplicated name' );
        testEl.removeChild(testEl.lastChild);
        assert_equals( testEl.properties.names.length, 1, 'length after removing a child' );
        assert_equals( testEl.properties.names[0], 'foo', 'item 0 after removing a child' );
}, 'names must update when adding property elements');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"></div>');
        var DSL = testEl.properties.names;
        assert_equals( DSL[0], 'foo', 'item 0 (before test)' );
        assert_equals( DSL[1], 'bar', 'item 1 (before test)' );
        testEl.appendChild(testEl.firstChild);
        assert_equals( DSL[0], 'bar', 'item 0 (after test)' );
        assert_equals( DSL[1], 'foo', 'item 1 (after test)' );
}, 'names must update when re-ordering property elements');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div></div>');
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 1, 'length (before test)' );
        assert_equals( DSL[0], 'foo', 'item 0 (before test)' );
        testEl.lastChild.itemProp.toggle('bar');
        assert_equals( DSL.length, 2, 'length after adding a token' );
        assert_equals( DSL[0], 'foo', 'item 0 after adding a token' );
        assert_equals( DSL[1], 'bar', 'item 1 after adding a token' );
        testEl.lastChild.itemProp.add('foo');
        assert_equals( DSL.length, 2, 'length after adding a duplicated token' );
        assert_equals( DSL[0], 'foo', 'item 0 after adding a duplicated token' );
        assert_equals( DSL[1], 'bar', 'item 1 after adding a duplicated token' );
        testEl.lastChild.removeAttribute('itemprop');
        assert_equals( DSL.length, 1, 'length after removing an attribute' );
        assert_equals( DSL[0], 'foo', 'item 0 after removing an attribute' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"></div><div itemprop="foo"></div>');
        DSL = testEl.properties.names;
        assert_equals( DSL.length, 2, 'length (before second test)' );
        assert_equals( DSL[0], 'foo', 'item 0 (before second test)' );
        assert_equals( DSL[1], 'bar', 'item 1 (before second test)' );
        testEl.firstChild.removeAttribute('itemprop');
        assert_equals( DSL.length, 2, 'length after removing attribute of first item' );
        assert_equals( DSL[0], 'bar', 'item 0 after removing attribute of first item' );
        assert_equals( DSL[1], 'foo', 'item 1 after removing attribute of first item' );
        testEl.firstChild.itemProp.add('foo');
        assert_equals( DSL.length, 2, 'length after adding duplicated token to first item' );
        assert_equals( DSL[0], 'foo', 'item 0 after adding duplicated token to first item' );
        assert_equals( DSL[1], 'bar', 'item 1 after adding duplicated token to first item' );
        testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo bar"></div>');
        DSL = testEl.properties.names;
        assert_equals( DSL.length, 2, 'length (before third test)' );
        assert_equals( DSL[0], 'foo', 'item 0 (before third test)' );
        assert_equals( DSL[1], 'bar', 'item 1 (before third test)' );
        testEl.firstChild.itemProp.toggle('foo');
        testEl.firstChild.itemProp.toggle('foo');
        assert_equals( DSL.length, 2, 'length after swapping tokens' );
        assert_equals( DSL[0], 'bar', 'item 0 after swapping tokens' );
        assert_equals( DSL[1], 'foo', 'item 1 after swapping tokens' );
}, 'names must update when changing itemProp of children');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 0, 'length (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( DSL.length, 2, 'length after id is created' );
        assert_equals( DSL[0], 'foo', 'item 0 after id is created' );
        assert_equals( DSL[1], 'bar', 'item 1 after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( DSL.length, 0, 'length after removing an attribute' );
        document.body.appendChild(parEl);
        var step1length = DSL.length;
        parEl.firstChild.id = 'id1';
        var step2length = DSL.length;
        var step2item0 = DSL[0];
        var step2item1 = DSL[1];
        parEl.firstChild.removeAttribute('id');
        var step3length = DSL.length;
        document.body.removeChild(parEl);
        assert_equals( step1length, 0, 'length (before test) when appended to document' );
        assert_equals( step2length, 2, 'length after id is created when appended to document' );
        assert_equals( step2item0, 'foo', 'item 0 after id is created when appended to document' );
        assert_equals( step2item1, 'bar', 'item 1 after id is created when appended to document' );
        assert_equals( step3length, 0, 'length after removing an attribute when appended to document' );
}, 'names must update when changing id of referenced sibling when appended to document');
test(function () {
        var parEl = makeEl('div',{},'<div itemprop="foo"><div itemprop="bar"></div></div><div itemscope itemref="id1"></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[1];
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 1, 'length (before test)' );
        assert_equals( DSL[0], 'baz', 'item 0 (before test)' );
        parEl.firstChild.id = 'id1';
        assert_equals( DSL.length, 2, 'length after id is created' );
        assert_equals( DSL[0], 'foo', 'item 0 after id is created' );
        assert_equals( DSL[1], 'bar', 'item 1 after id is created' );
        parEl.firstChild.removeAttribute('id');
        assert_equals( DSL.length, 1, 'length after removing an attribute' );
        assert_equals( DSL[0], 'baz', 'item 0 after removing an attribute' );
        document.body.appendChild(parEl);
        var step1length = DSL.length;
        var step1item0 = DSL[0];
        parEl.firstChild.id = 'id1';
        var step2length = DSL.length;
        var step2item0 = DSL[0];
        var step2item1 = DSL[1];
        parEl.firstChild.removeAttribute('id');
        var step3length = DSL.length;
        var step3item0 = DSL[0];
        document.body.removeChild(parEl);
        assert_equals( step1length, 1, 'length (before test)' );
        assert_equals( step1item0, 'baz', 'item 0 (before test)' );
        assert_equals( step2length, 2, 'length after id is created' );
        assert_equals( step2item0, 'foo', 'item 0 after id is created' );
        assert_equals( step2item1, 'bar', 'item 1 after id is created' );
        assert_equals( step3length, 1, 'length after removing an attribute' );
        assert_equals( step3item0, 'baz', 'item 0 after removing an attribute' );
}, 'names must update when changing duplicated id of referenced sibling');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope></div>');
        var testEl = parEl.childNodes[1];
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 0, 'length (before test)' );
        testEl.itemRef.toggle('id1');
        assert_equals( DSL.length, 1, 'length after itemref is changed' );
        assert_equals( DSL[0], 'foo', 'item 0 after itemref is changed' );
        testEl.removeAttribute('itemref');
        assert_equals( DSL.length, 0, 'length after itemref is removed' );
        assert_true( !DSL[0], 'item 0 after itemref is removed' );
        document.body.appendChild(parEl);
        var step1length = DSL.length;
        testEl.itemRef.toggle('id1');
        var step2length = DSL.length;
        var step2item = DSL[0];
        testEl.removeAttribute('itemref');
        var step3length = DSL.length;
        var step3item = DSL[0];
        document.body.removeChild(parEl);
        assert_equals( step1length, 0, 'length (before test)' );
        assert_equals( step2length, 1, 'length after itemref is changed' );
        assert_equals( step2item, 'foo', 'item 0 after itemref is changed' );
        assert_equals( step3length, 0, 'length after itemref is removed' );
        assert_true( !step3item, 'item 0 after itemref is removed' );
}, 'names must update when changing itemref to point to an element');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"><div></div></div><div itemscope itemref="id1"></div>');
        var testEl = parEl.childNodes[1];
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 0, 'length (before test)' );
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( DSL.length, 1, 'length after a referenced element is added' );
        assert_equals( DSL.item(0), 'foo', 'item 0 after a referenced element is added' ); //uses item just for the fun of it
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        assert_equals( DSL.length, 2, 'length after a referenced itemprop is changed' );
        assert_equals( DSL[0], 'bar', 'item 0 after a referenced element is added' );
        assert_equals( DSL[1], 'foo', 'item 1 after a referenced element is added' );
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        assert_equals( DSL.length, 1, 'length after a referenced element is removed' );
        assert_equals( DSL[0], 'foo', 'item 0 after a referenced element is removed' );
        parEl.innerHTML = '<div id="id1"><div></div></div><div itemscope itemref="id1"></div>';
        testEl = parEl.childNodes[1];
        DSL = testEl.properties.names;
        document.body.appendChild(parEl);
        var step1length = DSL.length;
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        var step2length = DSL.length;
        var step2item0 = DSL.item(0); //uses item just for the fun of it
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        var step3length = DSL.length;
        var step3item0 = DSL[0];
        var step3item1 = DSL[1];
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        var step4length = DSL.length;
        var step4item0 = DSL[0];
        document.body.removeChild(parEl);
        assert_equals( step1length, 0, 'length (before test)' );
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'foo'}));
        assert_equals( step2length, 1, 'length after a referenced element is added' );
        assert_equals( step2item0, 'foo', 'item 0 after a referenced element is added' ); //uses item just for the fun of it
        parEl.firstChild.firstChild.itemProp.toggle('bar');
        assert_equals( step3length, 2, 'length after a referenced itemprop is changed' );
        assert_equals( step3item0, 'bar', 'item 0 after a referenced element is added' );
        assert_equals( step3item1, 'foo', 'item 1 after a referenced element is added' );
        parEl.firstChild.removeChild(parEl.firstChild.firstChild);
        assert_equals( step4length, 1, 'length after a referenced element is removed' );
        assert_equals( step4item0, 'foo', 'item 0 after a referenced element is removed' );
}, 'names must update when changing children of elements referenced through itemref');
test(function () {
        var parEl = makeEl('div',{},'<div id="id1" itemprop="foo"></div><div itemscope itemref="id1"><div itemprop="bar"></div></div>');
        var testEl = parEl.childNodes[1];
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 2, 'length (before test)' );
        assert_equals( DSL[0], 'foo', 'item 0 (before test)' );
        assert_equals( DSL[1], 'bar', 'item 1 (before test)' );
        document.body.appendChild(testEl);
        var step1length = DSL.length;
        var step1prop0 = DSL[0];
        var step1prop1 = DSL[1];
        parEl.appendChild(testEl);
        assert_equals( step1length, 1, 'length after changing parent' );
        assert_equals( step1prop0, 'bar', 'item 0 after changing parent' );
        assert_true( !step1prop1, 'item 1 after changing parent' );
        assert_equals( DSL.length, 2, 'length after re-parenting' );
        assert_equals( DSL[0], 'foo', 'item 0 after re-parenting' );
        assert_equals( DSL[1], 'bar', 'item 1 after re-parenting' );
        document.body.appendChild(parEl);
        var step2length = DSL.length;
        var step2prop0 = DSL[0];
        var step2prop1 = DSL[1];
        document.createElement('div').appendChild(testEl);
        var step3length = DSL.length;
        var step3prop0 = DSL[0];
        var step3prop1 = DSL[1];
        parEl.appendChild(testEl);
        var step4length = DSL.length;
        var step4prop0 = DSL[0];
        var step4prop1 = DSL[1];
        document.body.removeChild(parEl);
        assert_equals( step2length, 2, 'length (before test) when appended to document' );
        assert_equals( step2prop0, 'foo', 'item 0 (before test) when appended to document' );
        assert_equals( step2prop1, 'bar', 'item 1 (before test) when appended to document' );
        assert_equals( step3length, 1, 'length after changing parent when appended to document' );
        assert_equals( step3prop0, 'bar', 'item 0 after changing parent when appended to document' );
        assert_true( !step3prop1, 'item 1 after changing parent when appended to document' );
        assert_equals( step4length, 2, 'length after re-parenting when appended to document' );
        assert_equals( step4prop0, 'foo', 'item 0 after re-parenting when appended to document' );
        assert_equals( step4prop1, 'bar', 'item 1 after re-parenting when appended to document' );
}, 'names must update when appending elements with itemref to different parents');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div><div itemprop="foo"></div></div>');
        var DSL = testEl.properties.names;
        assert_equals( DSL.length, 1, 'length (before test)' );
        assert_equals( DSL[0], 'foo', 'item 0 (before test)' );
        testEl.firstChild.itemScope = true;
        assert_equals( DSL.length, 0, 'length after setting itemscope' );
        assert_true( !DSL[0], 'item 0 after setting itemscope' );
        testEl.firstChild.removeAttribute('itemscope');
        assert_equals( DSL.length, 1, 'length after removing itemscope attribute' );
        assert_equals( DSL[0], 'foo', 'item 0 after removing itemscope attribute' );
}, 'names must update when changing itemscope of children');

/* potential bugs */
test(function () {
        var parEl = makeEl('div',{},'<div id="id1"></div>');
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1 id2'});
        parEl.appendChild(testEl);
        testEl.appendChild(makeEl('meta',{itemprop:'foo',content:'test'}));
        testEl.appendChild(makeEl('audio',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('embed',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('iframe',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('img',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('source',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('track',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('video',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('a',{itemprop:'foo',href:urls.base},'contained text'));
        testEl.appendChild(makeEl('area',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('link',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('object',{itemprop:'foo',data:urls.base},'contained text'));
        parEl.appendChild(makeEl('time',{itemprop:'bar',id:'id2'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('time',{itemprop:'foo',datetime:'test'},'te <span itemprop="foo" itemscope>st</span> ing'));
        parEl.firstChild.appendChild(makeEl('div',{itemprop:'baz'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('madeuponthespot',{itemprop:'baz'},'te <span itemprop="foo" itemscope>st</span> ing'));
        var properties, PNLfoo, PNLbar, PNLbaz, fooValues, barValues, bazValues, allArrays, snapshot = [];
        document.body.appendChild(parEl);
        try {
                properties = testEl.properties;
                PNLfoo = properties.namedItem('foo');
                PNLbar = properties.namedItem('bar');
                PNLbaz = properties.namedItem('baz');
                fooValues = PNLfoo.getValues();
                barValues = PNLbar.getValues();
                bazValues = PNLbaz.getValues();
                allArrays = [properties,PNLfoo,PNLbar,PNLbaz,fooValues,barValues,bazValues];
                for( var a = 0; a < allArrays.length; a++ ) {
                        snapshot[a] = [];
                        for( var b = 0; b < allArrays[a].length; b++ ) {
                                snapshot[a][b] = allArrays[a][b];
                        }
                }
        } catch(e) { /* need to clean up */ }
        document.body.removeChild(parEl);
        var c, d;
        for( c = 0; c < allArrays.length; c++ ) {
                for( d = 0; d < allArrays[c].length; d++ ) {
                        assert_equals( snapshot[c][d], allArrays[c][d], 'allArrays['+c+']['+d+']' );
                }
        }
        var newArrays = [testEl.properties,testEl.properties.namedItem('foo'),testEl.properties.namedItem('bar'),testEl.properties.namedItem('baz'),testEl.properties.namedItem('foo').getValues(),testEl.properties.namedItem('bar').getValues(),testEl.properties.namedItem('baz').getValues()];
        for( c = 0; c < newArrays.length; c++ ) {
                for( d = 0; d < newArrays[c].length; d++ ) {
                        assert_equals( snapshot[c][d], newArrays[c][d], 'newArrays['+c+']['+d+']' );
                }
        }
}, 'collections must survive the parent\'s removal from the document');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'});
        testEl.appendChild(makeEl('meta',{itemprop:'foo',content:'test'}));
        testEl.appendChild(makeEl('audio',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('embed',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('iframe',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('img',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('source',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('track',{itemprop:'foo',src:urls.base}));
        testEl.appendChild(makeEl('video',{itemprop:'foo',src:urls.base},'contained text'));
        testEl.appendChild(makeEl('a',{itemprop:'foo',href:urls.base},'contained text'));
        testEl.appendChild(makeEl('area',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('link',{itemprop:'foo',href:urls.base}));
        testEl.appendChild(makeEl('object',{itemprop:'foo',data:urls.base},'contained text'));
        testEl.appendChild(makeEl('time',{itemprop:'bar',id:'id2'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('time',{itemprop:'foo',datetime:'test'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('div',{itemprop:'baz'},'te <span itemprop="foo" itemscope>st</span> ing'));
        testEl.appendChild(makeEl('madeuponthespot',{itemprop:'baz'},'te <span itemprop="foo" itemscope>st</span> ing'));
        var properties, PNLfoo, PNLbar, PNLbaz, fooValues, barValues, bazValues, allArrays, snapshot = [];
        document.body.appendChild(testEl);
        try {
                properties = testEl.properties;
                PNLfoo = properties.namedItem('foo');
                PNLbar = properties.namedItem('bar');
                PNLbaz = properties.namedItem('baz');
                fooValues = PNLfoo.getValues();
                barValues = PNLbar.getValues();
                bazValues = PNLbaz.getValues();
                allArrays = [properties,PNLfoo,PNLbar,PNLbaz,fooValues,barValues,bazValues];
                for( var a = 0; a < allArrays.length; a++ ) {
                        snapshot[a] = [];
                        for( var b = 0; b < allArrays[a].length; b++ ) {
                                snapshot[a][b] = allArrays[a][b];
                        }
                }
        } catch(e) { /* need to clean up */ }
        document.body.removeChild(testEl);
        var c, d;
        for( c = 0; c < allArrays.length; c++ ) {
                for( d = 0; d < allArrays[c].length; d++ ) {
                        assert_equals( snapshot[c][d], allArrays[c][d], 'allArrays['+c+']['+d+']' );
                }
        }
        var newArrays = [testEl.properties,testEl.properties.namedItem('foo'),testEl.properties.namedItem('bar'),testEl.properties.namedItem('baz'),testEl.properties.namedItem('foo').getValues(),testEl.properties.namedItem('bar').getValues(),testEl.properties.namedItem('baz').getValues()];
        for( c = 0; c < newArrays.length; c++ ) {
                for( d = 0; d < newArrays[c].length; d++ ) {
                        assert_equals( snapshot[c][d], newArrays[c][d], 'newArrays['+c+']['+d+']' );
                }
        }
}, 'collections must survive the item\'s removal from the document');

/* override_builtins */
test(function () {
        //http://dev.w3.org/2006/webapi/WebIDL/#named-properties
        //[OverrideBuiltins] is not declared for any of the properties, hence no overriding is allowed
        var testEl = makeEl('div',{itemscope:'itemscope'});
        var namedItem = testEl.properties.namedItem;
        var item = testEl.properties.item;
        var names = testEl.properties.names;
        testEl.innerHTML = '<div itemprop="namedItem length item names"></div>';
        assert_equals( testEl.properties['namedItem'], namedItem, 'namedItem' );
        assert_equals( testEl.properties['length'], 1, 'length' );
        assert_equals( testEl.properties['item'], item, 'item' );
        assert_equals( testEl.properties['names'], names, 'names' );
}, 'itemprop names must not override builtin properties');

/* casting */
//when calling object[other_object], ECMAScript treats other_object as a named property so it casts it to a string using toString
//when looking up a named property, ECMAScript and WebIDL <http://dev.w3.org/2006/webapi/WebIDL/#named-properties> will prefer an array index property name
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="2"></div><div itemprop="0"></div>');
        assert_equals( testEl.properties.item('0'), testEl.properties.item(0), '0' );
        assert_equals( testEl.properties.item('2'), testEl.properties.item(2), '2' );
}, 'properties.item(integerString) should cast to a number');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="2"></div><div itemprop="0"></div>');
        assert_equals( testEl.properties['0'], testEl.properties.item(0), '0' );
        assert_equals( testEl.properties['2'], window.undefined, '2' );
}, 'properties[integerString] should act as a numeric index');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="0"></div>');
        assert_equals( testEl.properties.namedItem(0), testEl.properties.namedItem('0'), '0' );
        assert_true( testEl.properties.namedItem(0) instanceof PropertyNodeList , 'instanceof' );
}, 'properties.namedItem(integer) should cast to a string');
test(function () {
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="2 foo"></div><div itemprop="0"></div>');
        assert_equals( testEl.properties[{ toString: function(){return 'foo';}, valueOf: function(){return 1;} }][0], testEl.firstChild, 'foo' );
        assert_equals( testEl.properties[{ toString: function(){return '0';}, valueOf: function(){return 1;} }], testEl.firstChild, '0' );
        assert_equals( testEl.properties[{ toString: function(){return '2';}, valueOf: function(){return 0;} }], window.undefined, '2' );
}, 'properties[someObject] should cast toString before using whichever casting applies');

/* loops and evil itemref */
test(function () {
        //This should have 1 property on each itemscope, pointing only to its direct child
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemscope itemprop="foo"><div itemprop="bar"></div></div>');
        assert_equals( testEl.properties.length, 1, 'outer length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0]' );
        assert_true( !testEl.properties[1], 'outer properties[1]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 0, 'outer bar.length' );
        assert_true( !testEl.properties.namedItem('bar')[0], 'outer bar[0]' );
        assert_equals( testEl.properties.names.length, 1, 'outer names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'outer names[0]' );
        assert_true( !testEl.properties.names[1], 'outer names[1]' );
        assert_equals( testEl.firstChild.properties.length, 1, 'inner length' );
        assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0]' );
        assert_true( !testEl.firstChild.properties[1], 'inner properties[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[1], 'inner foo[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 1, 'inner names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0]' );
        assert_true( !testEl.firstChild.properties.names[1], 'inner names[1]' );
}, 'simple nested itemscope');
test(function () {
        //This should have 1 property on each itemscope, pointing only to its direct child
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1'},'<div itemscope itemprop="foo" id="id1" itemref="id2"><div itemprop="bar" id="id2"></div></div>');
        assert_equals( testEl.properties.length, 1, 'outer length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0]' );
        assert_true( !testEl.properties[1], 'outer properties[1]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 0, 'outer bar.length' );
        assert_true( !testEl.properties.namedItem('bar')[0], 'outer bar[0]' );
        assert_equals( testEl.properties.names.length, 1, 'outer names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'outer names[0]' );
        assert_true( !testEl.properties.names[1], 'outer names[1]' );
        assert_equals( testEl.firstChild.properties.length, 1, 'inner length' );
        assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0]' );
        assert_true( !testEl.firstChild.properties[1], 'inner properties[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[1], 'inner foo[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 1, 'inner names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0]' );
        assert_true( !testEl.firstChild.properties.names[1], 'inner names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 1, 'outer length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0] when appended to document' );
                assert_true( !testEl.properties[1], 'outer properties[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 0, 'outer bar.length when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[0], 'outer bar[0] when appended to document' );
                assert_equals( testEl.properties.names.length, 1, 'outer names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'outer names[0] when appended to document' );
                assert_true( !testEl.properties.names[1], 'outer names[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.length, 1, 'inner length when appended to document' );
                assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0] when appended to document' );
                assert_true( !testEl.firstChild.properties[1], 'inner properties[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[1], 'inner foo[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.names.length, 1, 'inner names.length when appended to document' );
                assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.names[1], 'inner names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'simple nested itemscope with itemref');
test(function () {
        //This should have 3 properties on the item; foo, bar and baz
        var parEl = makeEl('div',{},'<div itemprop="foo" id="id2"></div><div itemscope itemref="id1 id2"><div itemprop="bar"></div></div><div itemprop="baz" id="id1"></div>');
        var testEl = parEl.childNodes[1];
        assert_equals( testEl.properties.length, 3, 'length' );
        assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0]' );
        assert_equals( testEl.properties[1], testEl.firstChild, 'properties[1]' );
        assert_equals( testEl.properties[2], parEl.lastChild, 'properties[2]' );
        assert_true( !testEl.properties[3], 'properties[3]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], parEl.firstChild, 'foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar.length' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0]' );
        assert_true( !testEl.properties.namedItem('bar')[1], 'bar[1]' );
        assert_equals( testEl.properties.namedItem('baz').length, 1, 'baz.length' );
        assert_equals( testEl.properties.namedItem('baz')[0], parEl.lastChild, 'baz[0]' );
        assert_true( !testEl.properties.namedItem('baz')[1], 'baz[1]' );
        assert_equals( testEl.properties.names.length, 3, 'names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'names[0]' );
        assert_equals( testEl.properties.names[1], 'bar', 'names[1]' );
        assert_equals( testEl.properties.names[2], 'baz', 'names[2]' );
        assert_true( !testEl.properties.names[3], 'names[3]' );
        document.body.appendChild(parEl);
        try {
                assert_equals( testEl.properties.length, 3, 'length when appended to document' );
                assert_equals( testEl.properties[0], parEl.firstChild, 'properties[0] when appended to document' );
                assert_equals( testEl.properties[1], testEl.firstChild, 'properties[1] when appended to document' );
                assert_equals( testEl.properties[2], parEl.lastChild, 'properties[2] when appended to document' );
                assert_true( !testEl.properties[3], 'properties[3] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], parEl.firstChild, 'foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar.length when appended to document' );
                assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[1], 'bar[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('baz').length, 1, 'baz.length when appended to document' );
                assert_equals( testEl.properties.namedItem('baz')[0], parEl.lastChild, 'baz[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('baz')[1], 'baz[1] when appended to document' );
                assert_equals( testEl.properties.names.length, 3, 'names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'names[0] when appended to document' );
                assert_equals( testEl.properties.names[1], 'bar', 'names[1] when appended to document' );
                assert_equals( testEl.properties.names[2], 'baz', 'names[2] when appended to document' );
                assert_true( !testEl.properties.names[3], 'names[3] when appended to document' );
        } catch(e) {
                document.body.removeChild(parEl);
                throw (e);
        }
        document.body.removeChild(parEl);
}, 'simple sibling itemref');
test(function () {
        //This should have no properties
        var testEl = makeEl('div',{itemscope:'itemscope',id:'id1',itemref:'id1',itemprop:'foo'});
        assert_equals( testEl.properties.length, 0, 'length' );
        assert_true( !testEl.properties[0], 'properties[0]' );
        assert_equals( testEl.properties.namedItem('foo').length, 0, 'foo.length' );
        assert_true( !testEl.properties.namedItem('foo')[0], 'foo[0]' );
        assert_equals( testEl.properties.names.length, 0, 'names.length' );
        assert_true( !testEl.properties.names[0], 'names[0]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 0, 'length when appended to document' );
                assert_true( !testEl.properties[0], 'properties[0] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 0, 'foo.length when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[0], 'foo[0] when appended to document' );
                assert_equals( testEl.properties.names.length, 0, 'names.length when appended to document' );
                assert_true( !testEl.properties.names[0], 'names[0] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'itemref pointing to itself');
test(function () {
        //This should have 1 property, pointing to the child
        var testEl = makeEl('div',{itemscope:'itemscope',id:'id1',itemref:'id1',itemprop:'foo'},'<div itemprop="bar"></div>');
        assert_equals( testEl.properties.length, 1, 'length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0]' );
        assert_true( !testEl.properties[1], 'properties[1]' );
        assert_equals( testEl.properties.namedItem('foo').length, 0, 'foo.length' );
        assert_true( !testEl.properties.namedItem('foo')[0], 'foo[0]' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar.length' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0]' );
        assert_true( !testEl.properties.namedItem('bar')[1], 'bar[1]' );
        assert_equals( testEl.properties.names.length, 1, 'names.length' );
        assert_equals( testEl.properties.names[0], 'bar', 'names[0]' );
        assert_true( !testEl.properties.names[1], 'names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 1, 'length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'properties[0] when appended to document' );
                assert_true( !testEl.properties[1], 'properties[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 0, 'foo.length when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[0], 'foo[0] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 1, 'bar.length when appended to document' );
                assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[1], 'bar[1] when appended to document' );
                assert_equals( testEl.properties.names.length, 1, 'names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'bar', 'names[0] when appended to document' );
                assert_true( !testEl.properties.names[1], 'names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'itemref pointing to itself with child');
test(function () {
        //This should have 1 property on each itemscope, pointing only to its direct child
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemscope itemprop="foo" id="id1" itemref="id1"><div itemprop="bar"></div></div>');
        assert_equals( testEl.properties.length, 1, 'outer length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0]' );
        assert_true( !testEl.properties[1], 'outer properties[1]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 0, 'outer bar.length' );
        assert_true( !testEl.properties.namedItem('bar')[0], 'outer bar[0]' );
        assert_equals( testEl.properties.names.length, 1, 'outer names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'outer names[0]' );
        assert_true( !testEl.properties.names[1], 'outer names[1]' );
        assert_equals( testEl.firstChild.properties.length, 1, 'inner length' );
        assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0]' );
        assert_true( !testEl.firstChild.properties[1], 'inner properties[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'inner bar[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 1, 'inner names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0]' );
        assert_true( !testEl.firstChild.properties.names[1], 'inner names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 1, 'outer length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0] when appended to document' );
                assert_true( !testEl.properties[1], 'outer properties[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 0, 'outer bar.length when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[0], 'outer bar[0] when appended to document' );
                assert_equals( testEl.properties.names.length, 1, 'outer names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'outer names[0] when appended to document' );
                assert_true( !testEl.properties.names[1], 'outer names[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.length, 1, 'inner length when appended to document' );
                assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0] when appended to document' );
                assert_true( !testEl.firstChild.properties[1], 'inner properties[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'inner bar[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.names.length, 1, 'inner names.length when appended to document' );
                assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.names[1], 'inner names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'nested itemref pointing to itself with child');
test(function () {
        //Each itemscope has one property, pointing to the other one
        var testEl = makeEl('div',{},'<div id="id1" itemprop="foo" itemscope itemref="id2"></div><div id="id2" itemprop="bar" itemscope itemref="id1"></div>');
        assert_equals( testEl.firstChild.properties.length, 1, 'id1 length' );
        assert_equals( testEl.firstChild.properties[0], testEl.lastChild, 'id1 properties[0]' );
        assert_true( !testEl.firstChild.properties[1], 'id1 properties[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'id1 foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'id1 foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'id1 bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.lastChild, 'id1 bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'id1 bar[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 1, 'id1 names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'id1 names[0]' );
        assert_true( !testEl.firstChild.properties.names[1], 'id1 names[1]' );
        assert_equals( testEl.lastChild.properties.length, 1, 'id2 length' );
        assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'id2 properties[0]' );
        assert_true( !testEl.lastChild.properties[1], 'id2 properties[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('foo').length, 1, 'id2 foo.length' );
        assert_equals( testEl.lastChild.properties.namedItem('foo')[0], testEl.firstChild, 'id2 foo[0]' );
        assert_true( !testEl.lastChild.properties.namedItem('foo')[1], 'id2 foo[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'id2 bar.length' );
        assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'id2 bar[0]' );
        assert_equals( testEl.lastChild.properties.names.length, 1, 'id2 names.length' );
        assert_equals( testEl.lastChild.properties.names[0], 'foo', 'id2 names[0]' );
        assert_true( !testEl.lastChild.properties.names[1], 'id2 names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.firstChild.properties.length, 1, 'id1 length when appended to document' );
                assert_equals( testEl.firstChild.properties[0], testEl.lastChild, 'id1 properties[0] when appended to document' );
                assert_true( !testEl.firstChild.properties[1], 'id1 properties[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'id1 foo.length when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'id1 foo[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'id1 bar.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.lastChild, 'id1 bar[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'id1 bar[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.names.length, 1, 'id1 names.length when appended to document' );
                assert_equals( testEl.firstChild.properties.names[0], 'bar', 'id1 names[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.names[1], 'id1 names[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.length, 1, 'id2 length when appended to document' );
                assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'id2 properties[0] when appended to document' );
                assert_true( !testEl.lastChild.properties[1], 'id2 properties[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo').length, 1, 'id2 foo.length when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo')[0], testEl.firstChild, 'id2 foo[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('foo')[1], 'id2 foo[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'id2 bar.length when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'id2 bar[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.names.length, 1, 'id2 names.length when appended to document' );
                assert_equals( testEl.lastChild.properties.names[0], 'foo', 'id2 names[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.names[1], 'id2 names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'mutually referencing siblings');
test(function () {
        //Root has 2 properties, foo and bar
        //Each itemscope has one property, pointing to the other one
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div id="id1" itemprop="foo" itemscope itemref="id2"></div><div id="id2" itemprop="bar" itemscope itemref="id1"></div>');
        assert_equals( testEl.properties.length, 2, 'root length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'root properties[0]' );
        assert_equals( testEl.properties[1], testEl.lastChild, 'root properties[1]' );
        assert_true( !testEl.properties[2], 'root properties[2]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'root foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'root foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'root foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'root bar.length' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'root bar[0]' );
        assert_true( !testEl.properties.namedItem('bar')[1], 'root bar[1]' );
        assert_equals( testEl.properties.names.length, 2, 'root names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'root names[0]' );
        assert_equals( testEl.properties.names[1], 'bar', 'root names[1]' );
        assert_true( !testEl.properties.names[2], 'root names[2]' );
        assert_equals( testEl.firstChild.properties.length, 1, 'id1 length' );
        assert_equals( testEl.firstChild.properties[0], testEl.lastChild, 'id1 properties[0]' );
        assert_true( !testEl.firstChild.properties[1], 'id1 properties[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'id1 foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'id1 foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'id1 bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.lastChild, 'id1 bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'id1 bar[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 1, 'id1 names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'id1 names[0]' );
        assert_true( !testEl.firstChild.properties.names[1], 'id1 names[1]' );
        assert_equals( testEl.lastChild.properties.length, 1, 'id2 length' );
        assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'id2 properties[0]' );
        assert_true( !testEl.lastChild.properties[1], 'id2 properties[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('foo').length, 1, 'id2 foo.length' );
        assert_equals( testEl.lastChild.properties.namedItem('foo')[0], testEl.firstChild, 'id2 foo[0]' );
        assert_true( !testEl.lastChild.properties.namedItem('foo')[1], 'id2 foo[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'id2 bar.length' );
        assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'id2 bar[0]' );
        assert_equals( testEl.lastChild.properties.names.length, 1, 'id2 names.length' );
        assert_equals( testEl.lastChild.properties.names[0], 'foo', 'id2 names[0]' );
        assert_true( !testEl.lastChild.properties.names[1], 'id2 names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 2, 'root length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'root properties[0] when appended to document' );
                assert_equals( testEl.properties[1], testEl.lastChild, 'root properties[1] when appended to document' );
                assert_true( !testEl.properties[2], 'root properties[2] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'root foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'root foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'root foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 1, 'root bar.length when appended to document' );
                assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'root bar[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[1], 'root bar[1] when appended to document' );
                assert_equals( testEl.properties.names.length, 2, 'root names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'root names[0] when appended to document' );
                assert_equals( testEl.properties.names[1], 'bar', 'root names[1] when appended to document' );
                assert_true( !testEl.properties.names[2], 'root names[2] when appended to document' );
                assert_equals( testEl.firstChild.properties.length, 1, 'id1 length when appended to document' );
                assert_equals( testEl.firstChild.properties[0], testEl.lastChild, 'id1 properties[0] when appended to document' );
                assert_true( !testEl.firstChild.properties[1], 'id1 properties[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'id1 foo.length when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'id1 foo[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'id1 bar.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.lastChild, 'id1 bar[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'id1 bar[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.names.length, 1, 'id1 names.length when appended to document' );
                assert_equals( testEl.firstChild.properties.names[0], 'bar', 'id1 names[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.names[1], 'id1 names[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.length, 1, 'id2 length when appended to document' );
                assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'id2 properties[0] when appended to document' );
                assert_true( !testEl.lastChild.properties[1], 'id2 properties[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo').length, 1, 'id2 foo.length when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo')[0], testEl.firstChild, 'id2 foo[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('foo')[1], 'id2 foo[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'id2 bar.length when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'id2 bar[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.names.length, 1, 'id2 names.length when appended to document' );
                assert_equals( testEl.lastChild.properties.names[0], 'foo', 'id2 names[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.names[1], 'id2 names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'mutually referencing siblings with item parent');
test(function () {
        //Root has two properties, foo and bar
        //Bar has two properties, baz and qux
        //Qux has one property, bar
        var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div id="id1" itemprop="bar" itemscope><div itemprop="baz"></div><div itemprop="qux" itemscope itemref="id1"></div></div>');
        assert_equals( testEl.properties.length, 2, 'root length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'root properties[0]' );
        assert_equals( testEl.properties[1], testEl.lastChild, 'root properties[1]' );
        assert_true( !testEl.properties[2], 'root properties[2]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'root foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'root foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'root foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'root bar.length' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'root bar[0]' );
        assert_true( !testEl.properties.namedItem('bar')[1], 'root bar[1]' );
        assert_equals( testEl.properties.namedItem('baz').length, 0, 'root baz.length' );
        assert_true( !testEl.properties.namedItem('baz')[0], 'root baz[0]' );
        assert_equals( testEl.properties.namedItem('qux').length, 0, 'root qux.length' );
        assert_true( !testEl.properties.namedItem('qux')[0], 'root qux[0]' );
        assert_equals( testEl.properties.names.length, 2, 'root names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'root names[0]' );
        assert_equals( testEl.properties.names[1], 'bar', 'root names[1]' );
        assert_true( !testEl.properties.names[2], 'root names[2]' );
        assert_equals( testEl.lastChild.properties.length, 2, 'bar length' );
        assert_equals( testEl.lastChild.properties[0], testEl.lastChild.firstChild, 'bar properties[0]' );
        assert_equals( testEl.lastChild.properties[1], testEl.lastChild.lastChild, 'bar properties[1]' );
        assert_true( !testEl.lastChild.properties[2], 'bar properties[2]' );
        assert_equals( testEl.lastChild.properties.namedItem('foo').length, 0, 'bar foo.length' );
        assert_true( !testEl.lastChild.properties.namedItem('foo')[0], 'bar foo[0]' );
        assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'bar bar.length' );
        assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'bar bar[0]' );
        assert_equals( testEl.lastChild.properties.namedItem('baz').length, 1, 'bar baz.length' );
        assert_equals( testEl.lastChild.properties.namedItem('baz')[0], testEl.lastChild.firstChild, 'bar baz[0]' );
        assert_true( !testEl.lastChild.properties.namedItem('baz')[1], 'bar baz[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('qux').length, 1, 'bar qux.length' );
        assert_equals( testEl.lastChild.properties.namedItem('qux')[0], testEl.lastChild.lastChild, 'bar qux[0]' );
        assert_true( !testEl.lastChild.properties.namedItem('qux')[1], 'bar qux[1]' );
        assert_equals( testEl.lastChild.properties.names.length, 2, 'bar names.length' );
        assert_equals( testEl.lastChild.properties.names[0], 'baz', 'bar names[0]' );
        assert_equals( testEl.lastChild.properties.names[1], 'qux', 'bar names[1]' );
        assert_true( !testEl.lastChild.properties.names[2], 'bar names[2]' );
        assert_equals( testEl.lastChild.lastChild.properties.length, 1, 'qux length' );
        assert_equals( testEl.lastChild.lastChild.properties[0], testEl.lastChild, 'qux properties[0]' );
        assert_true( !testEl.lastChild.lastChild.properties[1], 'qux properties[1]' );
        assert_equals( testEl.lastChild.lastChild.properties.namedItem('foo').length, 0, 'qux foo.length' );
        assert_true( !testEl.lastChild.lastChild.properties.namedItem('foo')[0], 'qux foo[0]' );
        assert_equals( testEl.lastChild.lastChild.properties.namedItem('bar').length, 1, 'qux bar.length' );
        assert_equals( testEl.lastChild.lastChild.properties.namedItem('bar')[0], testEl.lastChild, 'qux bar[0]' );
        assert_true( !testEl.lastChild.lastChild.properties.namedItem('bar')[1], 'qux bar[1]' );
        assert_equals( testEl.lastChild.lastChild.properties.namedItem('baz').length, 0, 'qux baz.length' );
        assert_true( !testEl.lastChild.lastChild.properties.namedItem('baz')[0], 'qux baz[0]' );
        assert_equals( testEl.lastChild.lastChild.properties.namedItem('qux').length, 0, 'qux qux.length' );
        assert_true( !testEl.lastChild.lastChild.properties.namedItem('qux')[0], 'qux qux[0]' );
        assert_equals( testEl.lastChild.lastChild.properties.names.length, 1, 'qux names.length' );
        assert_equals( testEl.lastChild.lastChild.properties.names[0], 'bar', 'qux names[0]' );
        assert_true( !testEl.lastChild.lastChild.properties.names[1], 'qux names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 2, 'root length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'root properties[0] when appended to document' );
                assert_equals( testEl.properties[1], testEl.lastChild, 'root properties[1] when appended to document' );
                assert_true( !testEl.properties[2], 'root properties[2] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'root foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'root foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'root foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 1, 'root bar.length when appended to document' );
                assert_equals( testEl.properties.namedItem('bar')[0], testEl.lastChild, 'root bar[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[1], 'root bar[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('baz').length, 0, 'root baz.length when appended to document' );
                assert_true( !testEl.properties.namedItem('baz')[0], 'root baz[0] when appended to document' );
                assert_equals( testEl.properties.namedItem('qux').length, 0, 'root qux.length when appended to document' );
                assert_true( !testEl.properties.namedItem('qux')[0], 'root qux[0] when appended to document' );
                assert_equals( testEl.properties.names.length, 2, 'root names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'root names[0] when appended to document' );
                assert_equals( testEl.properties.names[1], 'bar', 'root names[1] when appended to document' );
                assert_true( !testEl.properties.names[2], 'root names[2] when appended to document' );
                assert_equals( testEl.lastChild.properties.length, 2, 'bar length when appended to document' );
                assert_equals( testEl.lastChild.properties[0], testEl.lastChild.firstChild, 'bar properties[0] when appended to document' );
                assert_equals( testEl.lastChild.properties[1], testEl.lastChild.lastChild, 'bar properties[1] when appended to document' );
                assert_true( !testEl.lastChild.properties[2], 'bar properties[2] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo').length, 0, 'bar foo.length when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('foo')[0], 'bar foo[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('bar').length, 0, 'bar bar.length when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('bar')[0], 'bar bar[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('baz').length, 1, 'bar baz.length when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('baz')[0], testEl.lastChild.firstChild, 'bar baz[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('baz')[1], 'bar baz[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('qux').length, 1, 'bar qux.length when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('qux')[0], testEl.lastChild.lastChild, 'bar qux[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('qux')[1], 'bar qux[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.names.length, 2, 'bar names.length when appended to document' );
                assert_equals( testEl.lastChild.properties.names[0], 'baz', 'bar names[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.names[1], 'qux', 'bar names[1] when appended to document' );
                assert_true( !testEl.lastChild.properties.names[2], 'bar names[2] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.length, 1, 'qux length when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties[0], testEl.lastChild, 'qux properties[0] when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties[1], 'qux properties[1] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.namedItem('foo').length, 0, 'qux foo.length when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties.namedItem('foo')[0], 'qux foo[0] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.namedItem('bar').length, 1, 'qux bar.length when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.namedItem('bar')[0], testEl.lastChild, 'qux bar[0] when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties.namedItem('bar')[1], 'qux bar[1] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.namedItem('baz').length, 0, 'qux baz.length when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties.namedItem('baz')[0], 'qux baz[0] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.namedItem('qux').length, 0, 'qux qux.length when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties.namedItem('qux')[0], 'qux qux[0] when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.names.length, 1, 'qux names.length when appended to document' );
                assert_equals( testEl.lastChild.lastChild.properties.names[0], 'bar', 'qux names[0] when appended to document' );
                assert_true( !testEl.lastChild.lastChild.properties.names[1], 'qux names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'itemref referencing parent item');
test(function () {
        //foo has one property, bar
        var testEl = makeEl('div',{id:'id1'},'<div itemprop="bar"></div><div itemscope itemref="id1" itemprop="foo"></div>');
        assert_equals( testEl.lastChild.properties.length, 1, 'length' );
        assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'properties[0]' );
        assert_true( !testEl.lastChild.properties[1], 'properties[1]' );
        assert_equals( testEl.lastChild.properties.namedItem('foo').length, 0, 'foo.length' );
        assert_true( !testEl.lastChild.properties.namedItem('foo')[0], 'foo[0]' );
        assert_equals( testEl.lastChild.properties.namedItem('bar').length, 1, 'bar.length' );
        assert_equals( testEl.lastChild.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0]' );
        assert_true( !testEl.lastChild.properties.namedItem('bar')[1], 'bar[1]' );
        assert_equals( testEl.lastChild.properties.names.length, 1, 'names.length' );
        assert_equals( testEl.lastChild.properties.names[0], 'bar', 'names[0]' );
        assert_true( !testEl.lastChild.properties.names[1], 'names[1]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.lastChild.properties.length, 1, 'length when appended to document' );
                assert_equals( testEl.lastChild.properties[0], testEl.firstChild, 'properties[0] when appended to document' );
                assert_true( !testEl.lastChild.properties[1], 'properties[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('foo').length, 0, 'foo.length when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('foo')[0], 'foo[0] when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('bar').length, 1, 'bar.length when appended to document' );
                assert_equals( testEl.lastChild.properties.namedItem('bar')[0], testEl.firstChild, 'bar[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.namedItem('bar')[1], 'bar[1] when appended to document' );
                assert_equals( testEl.lastChild.properties.names.length, 1, 'names.length when appended to document' );
                assert_equals( testEl.lastChild.properties.names[0], 'bar', 'names[0] when appended to document' );
                assert_true( !testEl.lastChild.properties.names[1], 'names[1] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'itemref referencing parent without itemscope');
test(function () {
        var testDiv = makeEl('div', {itemprop:'bar', id:'foo'}, '');
        var testSpan = makeEl('span', {itemscope:'itemscope', itemref: 'foo', id: 'foo'}, '');
        document.body.appendChild(testDiv);
        document.body.appendChild(testSpan);
        assert_equals(testSpan.properties.length, 1, 'has one property');
        assert_equals(testSpan.properties[0], testDiv, 'has first property');
        assert_equals(testSpan.properties.item(0), testDiv, 'has first property');
        assert_equals(testSpan.properties.namedItem('bar').length, 1, 'has 1 foo property');
        assert_equals(testSpan.properties.namedItem('bar').item(0), testDiv, 'div is foo property');
        assert_equals(testSpan.properties.names.length, 1, 'only has one property');
        document.body.removeChild(testDiv);
        document.body.removeChild(testSpan);
}, 'itemref referencing element with same id');
test(function () {
        //Root has three properties, foo, bar and baz
        //Foo has two properties, bar and baz
        var testEl = makeEl('div',{itemscope:'itemscope',itemref:'id1'},'<div itemscope itemprop="foo"><div itemprop="bar" id="id1"><div itemprop="baz"></div></div></div>');
        assert_equals( testEl.properties.length, 3, 'outer length' );
        assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0]' );
        assert_equals( testEl.properties[1], testEl.firstChild.firstChild, 'outer properties[1]' );
        assert_equals( testEl.properties[2], testEl.firstChild.firstChild.firstChild, 'outer properties[2]' );
        assert_true( !testEl.properties[3], 'outer properties[3]' );
        assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length' );
        assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0]' );
        assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1]' );
        assert_equals( testEl.properties.namedItem('bar').length, 1, 'outer bar.length' );
        assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'outer bar[0]' );
        assert_true( !testEl.properties.namedItem('bar')[1], 'outer bar[1]' );
        assert_equals( testEl.properties.namedItem('baz').length, 1, 'outer baz.length' );
        assert_equals( testEl.properties.namedItem('baz')[0], testEl.firstChild.firstChild.firstChild, 'outer baz[0]' );
        assert_true( !testEl.properties.namedItem('baz')[1], 'outer baz[1]' );
        assert_equals( testEl.properties.names.length, 3, 'outer names.length' );
        assert_equals( testEl.properties.names[0], 'foo', 'outer names[0]' );
        assert_equals( testEl.properties.names[1], 'bar', 'outer names[1]' );
        assert_equals( testEl.properties.names[2], 'baz', 'outer names[2]' );
        assert_true( !testEl.properties.names[3], 'outer names[3]' );
        assert_equals( testEl.firstChild.properties.length, 2, 'inner length' );
        assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0]' );
        assert_equals( testEl.firstChild.properties[1], testEl.firstChild.firstChild.firstChild, 'inner properties[1]' );
        assert_true( !testEl.firstChild.properties[2], 'inner properties[2]' );
        assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length' );
        assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0]' );
        assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length' );
        assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'inner bar[1]' );
        assert_equals( testEl.firstChild.properties.namedItem('baz').length, 1, 'inner baz.length' );
        assert_equals( testEl.firstChild.properties.namedItem('baz')[0], testEl.firstChild.firstChild.firstChild, 'inner baz[0]' );
        assert_true( !testEl.firstChild.properties.namedItem('baz')[1], 'inner baz[1]' );
        assert_equals( testEl.firstChild.properties.names.length, 2, 'inner names.length' );
        assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0]' );
        assert_equals( testEl.firstChild.properties.names[1], 'baz', 'inner names[1]' );
        assert_true( !testEl.firstChild.properties.names[2], 'inner names[2]' );
        document.body.appendChild(testEl);
        try {
                assert_equals( testEl.properties.length, 3, 'outer length when appended to document' );
                assert_equals( testEl.properties[0], testEl.firstChild, 'outer properties[0] when appended to document' );
                assert_equals( testEl.properties[1], testEl.firstChild.firstChild, 'outer properties[1] when appended to document' );
                assert_equals( testEl.properties[2], testEl.firstChild.firstChild.firstChild, 'outer properties[2] when appended to document' );
                assert_true( !testEl.properties[3], 'outer properties[3] when appended to document' );
                assert_equals( testEl.properties.namedItem('foo').length, 1, 'outer foo.length when appended to document' );
                assert_equals( testEl.properties.namedItem('foo')[0], testEl.firstChild, 'outer foo[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('foo')[1], 'outer foo[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('bar').length, 1, 'outer bar.length when appended to document' );
                assert_equals( testEl.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'outer bar[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('bar')[1], 'outer bar[1] when appended to document' );
                assert_equals( testEl.properties.namedItem('baz').length, 1, 'outer baz.length when appended to document' );
                assert_equals( testEl.properties.namedItem('baz')[0], testEl.firstChild.firstChild.firstChild, 'outer baz[0] when appended to document' );
                assert_true( !testEl.properties.namedItem('baz')[1], 'outer baz[1] when appended to document' );
                assert_equals( testEl.properties.names.length, 3, 'outer names.length when appended to document' );
                assert_equals( testEl.properties.names[0], 'foo', 'outer names[0] when appended to document' );
                assert_equals( testEl.properties.names[1], 'bar', 'outer names[1] when appended to document' );
                assert_equals( testEl.properties.names[2], 'baz', 'outer names[2] when appended to document' );
                assert_true( !testEl.properties.names[3], 'outer names[3] when appended to document' );
                assert_equals( testEl.firstChild.properties.length, 2, 'inner length when appended to document' );
                assert_equals( testEl.firstChild.properties[0], testEl.firstChild.firstChild, 'inner properties[0] when appended to document' );
                assert_equals( testEl.firstChild.properties[1], testEl.firstChild.firstChild.firstChild, 'inner properties[1] when appended to document' );
                assert_true( !testEl.firstChild.properties[2], 'inner properties[2] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('foo').length, 0, 'inner foo.length when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('foo')[0], 'inner foo[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar').length, 1, 'inner bar.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('bar')[0], testEl.firstChild.firstChild, 'inner bar[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('bar')[1], 'inner bar[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('baz').length, 1, 'inner baz.length when appended to document' );
                assert_equals( testEl.firstChild.properties.namedItem('baz')[0], testEl.firstChild.firstChild.firstChild, 'inner baz[0] when appended to document' );
                assert_true( !testEl.firstChild.properties.namedItem('baz')[1], 'inner baz[1] when appended to document' );
                assert_equals( testEl.firstChild.properties.names.length, 2, 'inner names.length when appended to document' );
                assert_equals( testEl.firstChild.properties.names[0], 'bar', 'inner names[0] when appended to document' );
                assert_equals( testEl.firstChild.properties.names[1], 'baz', 'inner names[1] when appended to document' );
                assert_true( !testEl.firstChild.properties.names[2], 'inner names[2] when appended to document' );
        } catch(e) {
                document.body.removeChild(testEl);
                throw (e);
        }
        document.body.removeChild(testEl);
}, 'itemref pointing to child of nested itemscope');

                </script>
        </body>
</html>
